package com.aliothservice.catfish.product.data;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeospatialIndex;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.web.multipart.MultipartFile;

import com.aliothservice.catfish.admin.data.PlatformRealtimeInfo;
import com.aliothservice.catfish.data.AndroidVersion;
import com.aliothservice.catfish.data.ProductCategory;
import com.aliothservice.catfish.data.TransportationTemperature;
import com.aliothservice.catfish.data.VehicleLengthDef;
import com.aliothservice.catfish.data.WarehouseTemperature;
import com.aliothservice.catfish.identity.data.User;
import com.aliothservice.catfish.product.request.AddDriverRequest;
import com.aliothservice.catfish.product.request.AddProductPendingRequest;
import com.aliothservice.catfish.product.request.AddWarehousePendingRequest;
import com.aliothservice.catfish.product.request.CancelCompleteVehicleServiceRequest;
import com.aliothservice.catfish.product.request.CancelProductServiceRequest;
import com.aliothservice.catfish.product.request.CancelWarehouseServiceRequest;
import com.aliothservice.catfish.product.request.CompleteVehicleServiceCountingRequest;
import com.aliothservice.catfish.product.request.ProductServiceCountingRequest;
import com.aliothservice.catfish.product.request.PublishCompleteVehicleServiceRequest;
import com.aliothservice.catfish.product.request.PublishProductServiceRequest;
import com.aliothservice.catfish.product.request.PublishWarehouseServiceRequest;
import com.aliothservice.catfish.product.request.SearchCompleteVehicleServiceRequest;
import com.aliothservice.catfish.product.request.SearchProductServiceRequest;
import com.aliothservice.catfish.product.request.SearchWarehouseServiceRequest;
import com.aliothservice.catfish.product.request.UpdateDriverRequest;
import com.aliothservice.catfish.product.request.UpdateProductInfoRequest;
import com.aliothservice.catfish.product.request.UpdateVehicleInfoRequest;
import com.aliothservice.catfish.product.request.UpdateWarehouseInfoRequest;
import com.aliothservice.catfish.product.request.WarehouseServiceCountingRequest;
import com.mongodb.WriteResult;
import com.mongodb.gridfs.GridFSDBFile;
import com.mongodb.gridfs.GridFSFile;

import net.coobird.thumbnailator.Thumbnails;

enum VehicleResourceType {
	RESOURCE_VEHICLE_PHOTO,
	RESOURCE_TRANSPORTATION_LICENSE,
	RESOURCE_INSURANCE,
	RESOURCE_VEHICLE_INSIDE_PHOTO
}

enum WarehouseResourceType {
	RESOURCE_WAREHOUSE_PHOTO,
	RESOURCE_WAREHOUSE_INSIDE_PHOTO
}

public class ProductDataManager {
	
	public static final int PREFERRED_VEHICLE_IMAGE_SIZE = 800;
	public static final int PREFERRED_WAREHOUSE_IMAGE_SIZE = 800;
	public static final int PREFERRED_PRODUCT_IMAGE_SIZE = 800;
	
	public VehiclePending findUpsertedVehiclePending(String phoneNumber) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(VehiclePending.STATUS_UPSERTED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		
		return mongoTemplate.findOne(query, VehiclePending.class);
	}
	
	public WarehousePending findUpsertedWarehousePending(String phoneNumber) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(WarehousePending.STATUS_UPSERTED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		
		return mongoTemplate.findOne(query, WarehousePending.class);
	}
	
	public ProductPending findUpsertedProductPending(String phoneNumber) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(ProductPending.STATUS_UPSERTED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		
		return mongoTemplate.findOne(query, ProductPending.class);
	}
	
	public boolean checkPresenceOfInReviewVehiclePending(String phoneNumber, String licensePlate) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria lpCriteria = Criteria.where("licensePlate").is(licensePlate);
		Criteria statusCriteria = Criteria.where("status").is(VehiclePending.STATUS_READY_FOR_REVIEW);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, lpCriteria, statusCriteria);
		query.addCriteria(criteria);
		
		return mongoTemplate.exists(query, VehiclePending.class);
	}
	
	public boolean checkPresenceOfInReviewWarehousePending(String phoneNumber, String name) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria nameCriteria = Criteria.where("name").is(name);
		Criteria statusCriteria = Criteria.where("status").is(WarehousePending.STATUS_READY_FOR_REVIEW);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, nameCriteria, statusCriteria);
		query.addCriteria(criteria);
		
		return mongoTemplate.exists(query, WarehousePending.class);
	}
	
	public boolean checkPresenceOfInReviewProductPending(String phoneNumber, String name) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria nameCriteria = Criteria.where("name").is(name);
		Criteria statusCriteria = Criteria.where("status").is(ProductPending.STATUS_READY_FOR_REVIEW);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, nameCriteria, statusCriteria);
		query.addCriteria(criteria);
		
		return mongoTemplate.exists(query, ProductPending.class);
	}
	
	public boolean checkPresenceOfApprovedVehicle(String phoneNumber, String licensePlate) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria lpCriteria = Criteria.where("licensePlate").is(licensePlate);
		Criteria statusCriteria = Criteria.where("status").is(Vehicle.VEHICLE_STATUS_APPROVED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, lpCriteria, statusCriteria);
		query.addCriteria(criteria);
		
		return mongoTemplate.exists(query, Vehicle.class);
	}
	
	public boolean checkPresenceOfApprovedWarehouse(String phoneNumber, String name) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria nameCriteria = Criteria.where("name").is(name);
		Criteria statusCriteria = Criteria.where("status").is(Warehouse.WAREHOUSE_STATUS_APPROVED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, nameCriteria, statusCriteria);
		query.addCriteria(criteria);
		
		return mongoTemplate.exists(query, Warehouse.class);
	}
	
	public boolean checkPresenceOfApprovedProduct(String phoneNumber, String name) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria nameCriteria = Criteria.where("name").is(name);
		Criteria statusCriteria = Criteria.where("status").is(Product.PRODUCT_STATUS_APPROVED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, nameCriteria, statusCriteria);
		query.addCriteria(criteria);
		
		return mongoTemplate.exists(query, Product.class);
	}
	
	public boolean checkPresenceOfOnlineVehicle(String phoneNumber, String vehicleId) {
		Query vehicleQuery = new Query();
		Criteria vehicleIdCriteria = Criteria.where("id").is(vehicleId);
		vehicleQuery.addCriteria(vehicleIdCriteria);
		Vehicle vehicle = mongoTemplate.findOne(vehicleQuery, Vehicle.class);
		if (null == vehicle) {
			return true;
		}
		
		Query specQuery = new Query();
		Criteria specUserCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria specVehicleCriteria = Criteria.where("vehicles").all(vehicle.getLicensePlate());
		Criteria specAndCriteria = new Criteria();
		specAndCriteria.andOperator(specUserCriteria, specVehicleCriteria);
		specQuery.addCriteria(specAndCriteria);
		VehicleSpec spec = mongoTemplate.findOne(specQuery, VehicleSpec.class);
		if (null == spec) {
			return true;
		}
		
		Query serviceQuery = new Query();
		Criteria serviceUserCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria serviceSpecCriteria = Criteria.where("keySpec").is(spec.getKeySpec());
		Criteria serviceStockCriteria = Criteria.where("stock").gt(Integer.valueOf(0));
		Criteria serviceAndCriteria = new Criteria();
		serviceAndCriteria.andOperator(serviceUserCriteria, serviceSpecCriteria, serviceStockCriteria);
		serviceQuery.addCriteria(serviceAndCriteria);
		
		return mongoTemplate.exists(serviceQuery, VehicleService.class);
	}
	
	public boolean checkPresenceOfOnlineWarehouse(String phoneNumber, String warehouseId) {
		Query warehouseQuery = new Query();
		Criteria warehouseIdCriteria = Criteria.where("id").is(warehouseId);
		warehouseQuery.addCriteria(warehouseIdCriteria);
		Warehouse warehouse = mongoTemplate.findOne(warehouseQuery, Warehouse.class);
		if (null == warehouse) {
			return true;
		}
		
		Query serviceQuery = new Query();
		Criteria serviceIdCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria serviceNameCriteria = Criteria.where("name").is(warehouse.getName());
		Criteria serviceStockCriteria = Criteria.where("stock").gt(Integer.valueOf(0));
		Criteria serviceAndCriteria = new Criteria();
		serviceAndCriteria.andOperator(serviceIdCriteria, serviceNameCriteria, serviceStockCriteria);
		serviceQuery.addCriteria(serviceAndCriteria);
		
		return mongoTemplate.exists(serviceQuery, WarehouseService.class);
	}
	
	public boolean checkPresenceOfOnlineProduct(String phoneNumber, String productId) {
		Query productQuery = new Query();
		Criteria productIdCriteria = Criteria.where("id").is(productId);
		productQuery.addCriteria(productIdCriteria);
		Product product = mongoTemplate.findOne(productQuery, Product.class);
		if (null == product) {
			return true;
		}
		
		Query serviceQuery = new Query();
		Criteria serviceIdCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria serviceNameCriteria = Criteria.where("name").is(product.getName());
		Criteria serviceStockCriteria = Criteria.where("stock").gt(Integer.valueOf(0));
		Criteria serviceAndCriteria = new Criteria();
		serviceAndCriteria.andOperator(serviceIdCriteria, serviceNameCriteria, serviceStockCriteria);
		serviceQuery.addCriteria(serviceAndCriteria);
		
		return mongoTemplate.exists(serviceQuery, ProductService.class);
	}
	
	public WarehouseService orderWarehouseServiceById(String phoneNumber, String serviceId, Date startDate, Date untilDate, Integer volume) {
		// locate service
		Query locateQuery = new Query();
		Criteria locateCriteria = Criteria.where("id").is(serviceId);
		Criteria sellerCriteria = Criteria.where("userId").ne(phoneNumber);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(locateCriteria, sellerCriteria);
		locateQuery.addCriteria(andCriteria);
		WarehouseService service = mongoTemplate.findOne(locateQuery, WarehouseService.class);
		if (null == service) {
			return null;
		}
		
		// try reduce online stock
		Query onlineQuery = new Query();
		Criteria idCriteria = Criteria.where("id").is(serviceId);
		Criteria startDateCriteria = Criteria.where("startDate").lte(startDate);
		Criteria untilDateCriteria = Criteria.where("untilDate").gte(untilDate);
		Criteria volumeCriteria = Criteria.where("stock").gte(volume);
		Criteria onlineAndCriteria = new Criteria();
		onlineAndCriteria.andOperator(idCriteria, startDateCriteria, untilDateCriteria, volumeCriteria);
		onlineQuery.addCriteria(onlineAndCriteria);
		Update onlineUpdate = new Update();
		onlineUpdate.inc("stock", Integer.valueOf(volume.intValue()*-1));
		WriteResult onlineResult = mongoTemplate.updateFirst(onlineQuery, onlineUpdate, WarehouseService.class);
		if (!onlineResult.isUpdateOfExisting()) {
			return null;
		}
		
		// try increase spec
		Query specQuery = new Query();
		Criteria keySpecCriteria = Criteria.where("keySpec").is(service.getKeySpec());
		Criteria specUserCriteria = Criteria.where("userId").is(service.getUserId());
		Criteria specAndCriteria = new Criteria();
		specAndCriteria.andOperator(keySpecCriteria, specUserCriteria);
		specQuery.addCriteria(specAndCriteria);
		Update specUpdate = new Update();
		specUpdate.inc("stock", volume);
		WriteResult specResult = mongoTemplate.updateFirst(specQuery, specUpdate, WarehouseSpec.class);
		if (!specResult.isUpdateOfExisting()) {
			return null;
		}
		
		return service;
	}
	
	public ProductService orderProductServiceById(String phoneNumber, String serviceId, Integer volume) {
		// locate service
		Query locateQuery = new Query();
		Criteria locateCriteria = Criteria.where("id").is(serviceId);
		Criteria sellerCriteria = Criteria.where("userId").ne(phoneNumber);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(locateCriteria, sellerCriteria);
		locateQuery.addCriteria(andCriteria);
		ProductService service = mongoTemplate.findOne(locateQuery, ProductService.class);
		if (null == service) {
			return null;
		}
		
		// try reduce online stock
		Query onlineQuery = new Query();
		Criteria idCriteria = Criteria.where("id").is(serviceId);
		Criteria volumeCriteria = Criteria.where("stock").gte(volume);
		Criteria onlineAndCriteria = new Criteria();
		onlineAndCriteria.andOperator(idCriteria, volumeCriteria);
		onlineQuery.addCriteria(onlineAndCriteria);
		Update onlineUpdate = new Update();
		onlineUpdate.inc("stock", Integer.valueOf(volume.intValue()*-1));
		WriteResult onlineResult = mongoTemplate.updateFirst(onlineQuery, onlineUpdate, ProductService.class);
		if (!onlineResult.isUpdateOfExisting()) {
			return null;
		}
		
		// try increase spec
		Query specQuery = new Query();
		Criteria keySpecCriteria = Criteria.where("keySpec").is(service.getKeySpec());
		Criteria specUserCriteria = Criteria.where("userId").is(service.getUserId());
		Criteria specAndCriteria = new Criteria();
		specAndCriteria.andOperator(keySpecCriteria, specUserCriteria);
		specQuery.addCriteria(specAndCriteria);
		Update specUpdate = new Update();
		specUpdate.inc("stock", Integer.valueOf(volume.intValue()*-1));
		WriteResult specResult = mongoTemplate.updateFirst(specQuery, specUpdate, ProductSpec.class);
		if (!specResult.isUpdateOfExisting()) {
			return null;
		}
		
		return service;
	}
	
	public VehicleService orderVehicleServiceBy(String phoneNumber, String serviceId, Date loadingDate) {
		// locate service
		logger.info("orderVehicleServiceBy - locate service, id:" + serviceId);
		Query locateQuery = new Query();
		Criteria locateCriteria = Criteria.where("id").is(serviceId);
		Criteria sellerCriteria = Criteria.where("userId").ne(phoneNumber);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(locateCriteria, sellerCriteria);
		locateQuery.addCriteria(andCriteria);
		VehicleService service = mongoTemplate.findOne(locateQuery, VehicleService.class);
		if (null == service) {
			logger.warn("orderVehicleServiceBy - cannot locate service, id:" + serviceId);
			return null;
		}
		
		// try reduce online stock
		Query onlineQuery = new Query();
		Criteria onlineUserCriteria = Criteria.where("userId").is(service.getUserId());
		Criteria onlineKeySpecCriteria = Criteria.where("keySpec").is(service.getKeySpec());
		Criteria onlineStockCriteria = Criteria.where("stock").gte(Integer.valueOf(1));
		Criteria onlineAndCriteria = new Criteria();
		onlineAndCriteria.andOperator(onlineUserCriteria, onlineKeySpecCriteria, onlineStockCriteria);
		onlineQuery.addCriteria(onlineAndCriteria);
		Update onlineUpdate = new Update();
		onlineUpdate.inc("stock", Integer.valueOf(-1));
		WriteResult onlineResult = mongoTemplate.updateMulti(onlineQuery, onlineUpdate, VehicleService.class);
		if (!onlineResult.isUpdateOfExisting()) {
			logger.warn("orderVehicleServiceBy - cannot reduct online stock for " + service.getStock());
			return null;
		}
		
		// try increase spec
		Query specQuery = new Query();
		Criteria keySpecCriteria = Criteria.where("keySpec").is(service.getKeySpec());
		Criteria specUserCriteria = Criteria.where("userId").is(service.getUserId());
		Criteria specAndCriteria = new Criteria();
		specAndCriteria.andOperator(keySpecCriteria, specUserCriteria);
		specQuery.addCriteria(specAndCriteria);
		Update specUpdate = new Update();
		specUpdate.inc("stock", Integer.valueOf(1));
		WriteResult specResult = mongoTemplate.updateFirst(specQuery, specUpdate, VehicleSpec.class);
		if (!specResult.isUpdateOfExisting()) {
			logger.warn("orderVehicleServiceBy - cannot increase spec for " + service.getStock());
			return null;
		}
		
		return service;
	}
	
	public boolean addVehiclePending(String phoneNumber, VehiclePending pending) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(VehiclePending.STATUS_UPSERTED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		
		Update update = new Update();
		update.set("userId", phoneNumber);
		update.set("licensePlate", pending.getLicensePlate());
		update.set("province", pending.getProvince());
		update.set("city", pending.getCity());
		update.set("purchaseDate", pending.getPurchaseDate());
		update.set("fullLoadWeight", pending.getFullLoadWeight());
		update.set("fullLoadMass", pending.getFullLoadMass());
		update.set("temperatureMin", pending.getTemperatureMin());
		update.set("temperatureMax", pending.getTemperatureMax());
		update.set("lengthDef", pending.getLengthDef());
		update.set("owner", pending.getOwner());
		update.set("odometer", pending.getOdometer());
		update.set("manInChargeName", pending.getManInChargeName());
		update.set("manInChargePhoneNumber", pending.getManInChargePhoneNumber());
		if (null != pending.getRefId()) {
			update.set("refId", pending.getRefId());
			Query rejectedQuery = new Query();
			Criteria rejectedIdCriteria = Criteria.where("id").is(pending.getRefId());
			rejectedQuery.addCriteria(rejectedIdCriteria);
			Vehicle rejectedVehicle = mongoTemplate.findOne(rejectedQuery, Vehicle.class);
			if (null != rejectedVehicle) {
				VehiclePending rejectedPending = this.findUpsertedVehiclePending(phoneNumber);
				if (null == rejectedPending) {
					update.set("vehiclePhotoId", rejectedVehicle.getVehiclePhotoId());
				} else {
					if (null == rejectedPending.getVehiclePhotoId()) {
						update.set("vehiclePhotoId", rejectedVehicle.getVehiclePhotoId());
					}
				}
				if (null == rejectedPending) {
					update.set("vehicleInsidePhotoId", rejectedVehicle.getVehicleInsidePhotoId());
				} else {
					if (null == rejectedPending.getVehicleInsidePhotoId()) {
						update.set("vehicleInsidePhotoId", rejectedVehicle.getVehicleInsidePhotoId());
					}
				}
				if (null == rejectedPending) {
					update.set("transportationLicenseImageId", rejectedVehicle.getTransportationLicenseImageId());
				} else {
					if (null == rejectedPending.getTransportationLicenseImageId()) {
						update.set("transportationLicenseImageId", rejectedVehicle.getTransportationLicenseImageId());
					}
				}
				if (null == rejectedPending) {
					update.set("insuranceImageId", rejectedVehicle.getInsuranceImageId());
				} else {
					if (null == rejectedPending.getInsuranceImageId()) {
						update.set("insuranceImageId", rejectedVehicle.getInsuranceImageId());
					}
				}
			} else {
				logger.error("addVehiclePending - cannot locate rejected vehicle: " + pending.getRefId());
			}
		}
		
		WriteResult result = mongoTemplate.upsert(query, update, VehiclePending.class);
		if (1 != result.getN()) {
			return false;
		}
		
		return true;
	}
	
	public boolean addWarehousePending(String phoneNumber, AddWarehousePendingRequest req) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(WarehousePending.STATUS_UPSERTED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		
		Update update = new Update();
		update.set("userId", phoneNumber);
		update.set("name", req.getName());
		update.set("owner", req.getOwner());
		update.set("ownerPhoneNumber", req.getOwnerPhoneNumber());
		update.set("province", req.getProvince());
		update.set("city", req.getCity());
		update.set("district", req.getDistrict());
		update.set("address", req.getAddress());
		update.set("temperatureLevels", req.getTemperatureLevels());
		DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
		try {
			update.set("foundDate", formatter.parse(req.getFoundDate()));
		} catch (ParseException e) {
			if (logger.isErrorEnabled()) {
				logger.error("addWarehousePending - invalid found date: " + req.getFoundDate());
			}
			return false;
		}
		update.set("manInChargeName", req.getManInChargeName());
		update.set("manInChargePhoneNumber", req.getManInChargePhoneNumber());
		update.set("status", WarehousePending.STATUS_UPSERTED);
		update.set("lastUpdateTime", new Date());
		if (null != req.getRefId()) {
			update.set("refId", req.getRefId());
			Query rejectedQuery = new Query();
			Criteria rejectedIdCriteria = Criteria.where("id").is(req.getRefId());
			rejectedQuery.addCriteria(rejectedIdCriteria);
			Warehouse rejectedWarehouse = mongoTemplate.findOne(rejectedQuery, Warehouse.class);
			if (null != rejectedWarehouse) {
				WarehousePending pending = this.findUpsertedWarehousePending(phoneNumber);
				if (null == pending) {
					update.set("warehousePhotoId", rejectedWarehouse.getWarehousePhotoId());
				} else {
					if (null == pending.getWarehousePhotoId()) {
						update.set("warehousePhotoId", rejectedWarehouse.getWarehousePhotoId());
					}
				}
				if (null == pending) {
					update.set("warehouseInsidePhotoId", rejectedWarehouse.getWarehouseInsidePhotoId());
				} else {
					if (null == pending.getWarehouseInsidePhotoId()) {
						update.set("warehouseInsidePhotoId", rejectedWarehouse.getWarehouseInsidePhotoId());
					}
				}
			} else {
				logger.error("addWarehousePending - cannot locate rejected warehouse: " + req.getRefId());
			}
		}
		
		WriteResult result = mongoTemplate.upsert(query, update, WarehousePending.class);
		if (1 != result.getN()) {
			return false;
		}
		
		return true;
	}
	
	public boolean addProductPending(String phoneNumber, AddProductPendingRequest req) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(ProductPending.STATUS_UPSERTED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		
		Update update = new Update();
		update.set("userId", phoneNumber);
		update.set("name", req.getName());
		update.set("category", req.getCategory());
		update.set("ownerName", req.getOwnerName());
		update.set("ownerPhoneNumber", req.getOwnerPhoneNumber());
		update.set("province", req.getProvince());
		update.set("city", req.getCity());
		update.set("district", req.getDistrict());
		update.set("originProvince", req.getOriginProvince());
		update.set("originCity", req.getOriginCity());
		update.set("originDistrict", req.getOriginDistrict());
		update.set("manInChargeName", req.getManInChargeName());
		update.set("manInChargePhoneNumber", req.getManInChargePhoneNumber());
		update.set("status", ProductPending.STATUS_UPSERTED);
		update.set("lastUpdateTime", new Date());
		if (null != req.getRefId()) {
			update.set("refId", req.getRefId());
			Query rejectedQuery = new Query();
			Criteria rejectedIdCriteria = Criteria.where("id").is(req.getRefId());
			rejectedQuery.addCriteria(rejectedIdCriteria);
			Product rejectedProduct = mongoTemplate.findOne(rejectedQuery, Product.class);
			if (null != rejectedProduct) {
				ProductPending pending = this.findUpsertedProductPending(phoneNumber);
				if (null == pending) {
					update.set("productPhotoId", rejectedProduct.getProductPhotoId());
				} else {
					if (null == pending.getProductPhotoId()) {
						update.set("productPhotoId", rejectedProduct.getProductPhotoId());
					}
				}
			} else {
				logger.error("addProductPending - cannot locate rejected product: " + req.getRefId());
			}
		}
		
		WriteResult result = mongoTemplate.upsert(query, update, ProductPending.class);
		if (1 != result.getN()) {
			return false;
		}
		
		return true;
	}
	
	public String completeVehiclePending(VehiclePending pending) {
		if (null != pending.getRefId()) {
			logger.info("completeUpsertedVehiclePending - re-submit rejected pending: " + pending.getRefId());
			Query removeQuery = new Query();
			Criteria removeIdCriteria = Criteria.where("id").is(pending.getRefId());
			Criteria removeStatusCriteria = Criteria.where("status").is(Vehicle.VEHICLE_STATUS_REJECTED);
			Criteria userCriteria = Criteria.where("userId").is(pending.getUserId());
			Criteria removeAndCriteria = new Criteria();
			removeAndCriteria.andOperator(removeIdCriteria, removeStatusCriteria, userCriteria);
			removeQuery.addCriteria(removeAndCriteria);
			WriteResult removeResult = mongoTemplate.remove(removeQuery, Vehicle.class);
			if (1 != removeResult.getN()) {
				logger.error("completeUpsertedVehiclePending - cannot remove re-submitted pending: " + pending.getRefId());
				return null;
			}
		}
		
		Query query = new Query();
		Criteria criteria = Criteria.where("id").is(pending.getId());
		query.addCriteria(criteria);
		Update update = new Update();
		update.set("lastUpdateTime", new Date());
		update.set("status", VehiclePending.STATUS_READY_FOR_REVIEW);
		
		WriteResult result = mongoTemplate.updateFirst(query, update, VehiclePending.class);
		if (1 != result.getN()) {
			return null;
		}
		
		// generate real time info
		PlatformRealtimeInfo realtimeInfo = new PlatformRealtimeInfo();
		realtimeInfo.setModule(PlatformRealtimeInfo.MODULE_SERVICE);
		realtimeInfo.setIdentifier(PlatformRealtimeInfo.IDENTIFIER_VEHICLE);
		realtimeInfo.setVehicleLicensePlate(pending.getLicensePlate());
		realtimeInfo.setVehicleLocation(pending.getProvince()+pending.getCity());
		realtimeInfo.setVehicleContactName(pending.getManInChargeName());
		realtimeInfo.setVehicleContactPhoneNumber(pending.getManInChargePhoneNumber());
		realtimeInfo.setCreationDate(LocalDateTime.now());
		mongoTemplate.save(realtimeInfo);
		
		return pending.getId();
	}
	
	public String completeWarehousePending(WarehousePending pending) {
		if (null != pending.getRefId()) {
			logger.info("completeWarehousePending - re-submit rejected pending: " + pending.getRefId());
			Query removeQuery = new Query();
			Criteria removeIdCriteria = Criteria.where("id").is(pending.getRefId());
			Criteria removeStatusCriteria = Criteria.where("status").is(Warehouse.WAREHOUSE_STATUS_REJECTED);
			Criteria userCriteria = Criteria.where("userId").is(pending.getUserId());
			Criteria removeAndCriteria = new Criteria();
			removeAndCriteria.andOperator(removeIdCriteria, removeStatusCriteria, userCriteria);
			removeQuery.addCriteria(removeAndCriteria);
			WriteResult removeResult = mongoTemplate.remove(removeQuery, Warehouse.class);
			if (1 != removeResult.getN()) {
				logger.error("completeWarehousePending - cannot remove re-submitted pending: " + pending.getRefId());
				return null;
			}
		}
		Query query = new Query();
		Criteria criteria = Criteria.where("id").is(pending.getId());
		query.addCriteria(criteria);
		Update update = new Update();
		update.set("status", WarehousePending.STATUS_READY_FOR_REVIEW);
		update.set("lastUpdateTime", new Date());
		
		WriteResult result = mongoTemplate.updateFirst(query, update, WarehousePending.class);
		if (1 != result.getN()) {
			return null;
		}
		
		// generate real time info
		PlatformRealtimeInfo realtimeInfo = new PlatformRealtimeInfo();
		realtimeInfo.setModule(PlatformRealtimeInfo.MODULE_SERVICE);
		realtimeInfo.setIdentifier(PlatformRealtimeInfo.IDENTIFIER_WAREHOUSE);
		realtimeInfo.setWarehouseName(pending.getName());
		realtimeInfo.setWarehouseLocation(pending.getProvince()+pending.getCity()+pending.getDistrict());
		realtimeInfo.setWarehouseContactName(pending.getManInChargeName());
		realtimeInfo.setWarehouseContactPhoneNumber(pending.getManInChargePhoneNumber());
		realtimeInfo.setCreationDate(LocalDateTime.now());
		mongoTemplate.save(realtimeInfo);
		
		return pending.getId();
	}
	
	public boolean completeProductPending(ProductPending pending) {
		if (null != pending.getRefId()) {
			logger.info("completeProductPending - re-submit rejected pending: " + pending.getRefId());
			Query removeQuery = new Query();
			Criteria removeIdCriteria = Criteria.where("id").is(pending.getRefId());
			Criteria removeStatusCriteria = Criteria.where("status").is(Product.PRODUCT_STATUS_REJECTED);
			Criteria userCriteria = Criteria.where("userId").is(pending.getUserId());
			Criteria removeAndCriteria = new Criteria();
			removeAndCriteria.andOperator(removeIdCriteria, removeStatusCriteria, userCriteria);
			removeQuery.addCriteria(removeAndCriteria);
			WriteResult removeResult = mongoTemplate.remove(removeQuery, Product.class);
			if (1 != removeResult.getN()) {
				logger.error("completeProductPending - cannot remove re-submitted pending: " + pending.getRefId());
				return false;
			}
		}
		
		Query query = new Query();
		Criteria criteria = Criteria.where("id").is(pending.getId());
		query.addCriteria(criteria);
		Update update = new Update();
		update.set("status", ProductPending.STATUS_READY_FOR_REVIEW);
		update.set("lastUpdateTime", new Date());
		
		WriteResult result = mongoTemplate.updateFirst(query, update, ProductPending.class);
		if (1 != result.getN()) {
			return false;
		}
		
		// generate real time info
		PlatformRealtimeInfo realtimeInfo = new PlatformRealtimeInfo();
		realtimeInfo.setModule(PlatformRealtimeInfo.MODULE_SERVICE);
		realtimeInfo.setIdentifier(PlatformRealtimeInfo.IDENTIFIER_PRODUCT);
		realtimeInfo.setProductTitle(pending.getName());
		realtimeInfo.setProductOrigin(pending.getOriginProvince()+pending.getOriginCity()+pending.getOriginDistrict());
		realtimeInfo.setProductContactName(pending.getManInChargeName());
		realtimeInfo.setProductContactPhoneNumber(pending.getManInChargePhoneNumber());
		realtimeInfo.setCreationDate(LocalDateTime.now());
		mongoTemplate.save(realtimeInfo);
		
		return true;
	}
	
	public String updateVehicleResource(String phoneNumber, String vehicleId, String type, MultipartFile file) throws IOException {
		Query locateQuery = new Query();
		Criteria locateIdCriteria = Criteria.where("id").is(vehicleId);
		Criteria locateUserCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria locateAndCriteria = new Criteria();
		locateAndCriteria.andOperator(locateIdCriteria, locateUserCriteria);
		locateQuery.addCriteria(locateAndCriteria);
		Vehicle vehicle = mongoTemplate.findOne(locateQuery, Vehicle.class);
		if (null == vehicle) {
			logger.warn("updateVehicleResource - cannot locate vehicle by id: " + vehicleId);
			return null;
		}
		
		// scale the image and store
		BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
		if (logger.isDebugEnabled()) {
			logger.debug("processing image, width:" + bufferedImage.getWidth() + ", height:" + bufferedImage.getHeight());
		}
		BufferedImage scaledBufferedImage = null;
		if ((bufferedImage.getWidth()>PREFERRED_VEHICLE_IMAGE_SIZE) || (bufferedImage.getHeight()>PREFERRED_VEHICLE_IMAGE_SIZE)) {
			scaledBufferedImage = Thumbnails.of(bufferedImage).size(PREFERRED_VEHICLE_IMAGE_SIZE, PREFERRED_VEHICLE_IMAGE_SIZE).asBufferedImage();
			logger.debug("processing image, scaled width:" + scaledBufferedImage.getWidth() + ", scaled height:" + scaledBufferedImage.getHeight());
		}
		GridFSFile storedFile = null;
		if (null != scaledBufferedImage) {
			logger.debug("store scaled image");
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			Thumbnails.of(scaledBufferedImage).scale(1.0).outputFormat("jpg").toOutputStream(outputStream);
			ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
			storedFile = gridFsTemplate.store(inputStream, phoneNumber+type);
		} else {
			logger.debug("store original image");
			storedFile = gridFsTemplate.store(file.getInputStream(), phoneNumber+type);
		}
		if (null == storedFile) {
			return null;
		}
		
		if (0 == type.compareTo("transportationLicense")) {
			// clean up
			Query deleteQuery = new Query();
			Criteria deleteCriteria = Criteria.where("_id").is(vehicle.getTransportationLicenseImageId());
			deleteQuery.addCriteria(deleteCriteria);
			gridFsTemplate.delete(deleteQuery);
			
			// update
			Update update = new Update();
			update.set("transportationLicenseImageId", storedFile.getId().toString());
			update.set("lastUpdateTime", new Date());
			WriteResult updateResult = mongoTemplate.updateFirst(locateQuery, update, Vehicle.class);
			if (!updateResult.isUpdateOfExisting()) {
				logger.warn("updateVehicleResource - cannot update image for vehicle, identified by: " + vehicleId);
				return null;
			}
		} else {
			// clean up
			Query deleteQuery = new Query();
			Criteria deleteCriteria = Criteria.where("_id").is(vehicle.getInsuranceImageId());
			deleteQuery.addCriteria(deleteCriteria);
			gridFsTemplate.delete(deleteQuery);
			
			// update
			Update update = new Update();
			update.set("insuranceImageId", storedFile.getId().toString());
			update.set("lastUpdateTime", new Date());
			WriteResult updateResult = mongoTemplate.updateFirst(locateQuery, update, Vehicle.class);
			if (!updateResult.isUpdateOfExisting()) {
				logger.warn("updateVehicleResource - cannot update image for vehicle, identified by: " + vehicleId);
				return null;
			}
		}
		
		return storedFile.getId().toString();
	}
	
	public String addVehiclePendingResource(String phoneNumber, String type, MultipartFile file) throws IOException {
		String filename = phoneNumber + "_" + type;
		VehicleResourceType resourceType;
		if (0 == type.compareTo("vehicle")) {
			resourceType = VehicleResourceType.RESOURCE_VEHICLE_PHOTO;
		} else if (0 == type.compareTo("transportationLicense")) {
			resourceType = VehicleResourceType.RESOURCE_TRANSPORTATION_LICENSE;
		} else if (0 == type.compareTo("vehicleInside")) {
			resourceType = VehicleResourceType.RESOURCE_VEHICLE_INSIDE_PHOTO;
		} else {
			resourceType = VehicleResourceType.RESOURCE_INSURANCE;
		}
		
		// remove unlinked image file
		VehiclePending pending = findUpsertedVehiclePending(phoneNumber);
		if (null != pending) {
			switch(resourceType) {
				case RESOURCE_VEHICLE_PHOTO:
					if (null != pending.getVehiclePhotoId()) {
						Query deleteQuery = new Query();
						Criteria deleteCriteria = Criteria.where("_id").is(pending.getVehiclePhotoId());
						deleteQuery.addCriteria(deleteCriteria);
						gridFsTemplate.delete(deleteQuery);
					}
					break;
				case RESOURCE_TRANSPORTATION_LICENSE:
					if (null != pending.getTransportationLicenseImageId()) {
						Query deleteQuery = new Query();
						Criteria deleteCriteria = Criteria.where("_id").is(pending.getTransportationLicenseImageId());
						deleteQuery.addCriteria(deleteCriteria);
						gridFsTemplate.delete(deleteQuery);
					}
					break;
				case RESOURCE_INSURANCE:
					if (null != pending.getInsuranceImageId()) {
						Query deleteQuery = new Query();
						Criteria deleteCriteria = Criteria.where("_id").is(pending.getInsuranceImageId());
						deleteQuery.addCriteria(deleteCriteria);
						gridFsTemplate.delete(deleteQuery);
					}
					break;
				case RESOURCE_VEHICLE_INSIDE_PHOTO:
					if (null != pending.getVehicleInsidePhotoId()) {
						Query deleteQuery = new Query();
						Criteria deleteCriteria = Criteria.where("_id").is(pending.getVehicleInsidePhotoId());
						deleteQuery.addCriteria(deleteCriteria);
						gridFsTemplate.delete(deleteQuery);
					}
					break;
				default:
					// never goes here
					break;
			}
		}
		
		// store the file
		boolean shouldStore = false;
		BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
		if (logger.isDebugEnabled()) {
			logger.debug("processing image <filename:" + filename + ">, width:" + bufferedImage.getWidth() + ", height:" + bufferedImage.getHeight());
		}
		BufferedImage scaledBufferedImage = null;
		if ((resourceType==VehicleResourceType.RESOURCE_VEHICLE_PHOTO) || (resourceType==VehicleResourceType.RESOURCE_VEHICLE_INSIDE_PHOTO)) {
			if (bufferedImage.getWidth() == bufferedImage.getHeight()) {
				// scale image
				if ((bufferedImage.getWidth()>PREFERRED_VEHICLE_IMAGE_SIZE) || (bufferedImage.getHeight()>PREFERRED_VEHICLE_IMAGE_SIZE)) {
					scaledBufferedImage = Thumbnails.of(bufferedImage).size(PREFERRED_VEHICLE_IMAGE_SIZE, PREFERRED_VEHICLE_IMAGE_SIZE).asBufferedImage();
					logger.debug("processing image <filename:" + filename + ">, scaled width:" + scaledBufferedImage.getWidth() + ", scaled height:" + scaledBufferedImage.getHeight());
				}
				shouldStore = true;
			} else {
				// resize and store original image and will be cropped by client application later
				if ((bufferedImage.getWidth()>PREFERRED_VEHICLE_IMAGE_SIZE) && (bufferedImage.getHeight()>PREFERRED_VEHICLE_IMAGE_SIZE)) {
					int scaleFactor;
					if (bufferedImage.getWidth() > bufferedImage.getHeight()) {
						scaleFactor = bufferedImage.getWidth()*PREFERRED_VEHICLE_IMAGE_SIZE/bufferedImage.getHeight();
					} else {
						scaleFactor = bufferedImage.getHeight()*PREFERRED_VEHICLE_IMAGE_SIZE/bufferedImage.getWidth();
					}
					scaledBufferedImage = Thumbnails.of(bufferedImage).size(scaleFactor, scaleFactor).asBufferedImage();
					logger.debug("resized original image, width:" + scaledBufferedImage.getWidth() + " height:" + scaledBufferedImage.getHeight());
				}
			}
		} else {
			if ((bufferedImage.getWidth()>PREFERRED_VEHICLE_IMAGE_SIZE) || (bufferedImage.getHeight()>PREFERRED_VEHICLE_IMAGE_SIZE)) {
				scaledBufferedImage = Thumbnails.of(bufferedImage).size(PREFERRED_VEHICLE_IMAGE_SIZE, PREFERRED_VEHICLE_IMAGE_SIZE).asBufferedImage();
				logger.debug("processing image <filename:" + filename + ">, scaled width:" + scaledBufferedImage.getWidth() + ", scaled height:" + scaledBufferedImage.getHeight());
			}
		}
		GridFSFile storedFile = null;
		if (null != scaledBufferedImage) {
			logger.debug("store scaled image");
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			Thumbnails.of(scaledBufferedImage).scale(1.0).outputFormat("jpg").toOutputStream(outputStream);
			ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
			storedFile = gridFsTemplate.store(inputStream, filename);
		} else {
			logger.debug("store original image");
			storedFile = gridFsTemplate.store(file.getInputStream(), filename);
		}
		if (null == storedFile) {
			return null;
		}
		
		// update vehicle pending
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(VehiclePending.STATUS_UPSERTED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		Update update = new Update();
		update.set("lastUpdateTime", new Date());
		if (null == pending) {
			update.set("userId", phoneNumber);
			update.set("status", VehiclePending.STATUS_UPSERTED);
		}
		switch(resourceType) {
			case RESOURCE_VEHICLE_PHOTO:
				if (shouldStore) {
					update.set("vehiclePhotoId", storedFile.getId().toString());
				}
				break;
			case RESOURCE_TRANSPORTATION_LICENSE:
				update.set("transportationLicenseImageId", storedFile.getId().toString());
				break;
			case RESOURCE_INSURANCE:
				update.set("insuranceImageId", storedFile.getId().toString());
				break;
			case RESOURCE_VEHICLE_INSIDE_PHOTO:
				if (shouldStore) {
					update.set("vehicleInsidePhotoId", storedFile.getId().toString());
				}
				break;
			default:
				break;
		}
		WriteResult result = mongoTemplate.upsert(query, update, VehiclePending.class);
		if (1 != result.getN()) {
			return null;
		}
		
		if ((resourceType==VehicleResourceType.RESOURCE_VEHICLE_PHOTO) || (resourceType==VehicleResourceType.RESOURCE_VEHICLE_INSIDE_PHOTO)) {
			if (bufferedImage.getWidth() == bufferedImage.getHeight()) {
				return storedFile.getId().toString();
			} else {
				if (null != scaledBufferedImage) {
					// resized
					return storedFile.getId().toString() + ":" + scaledBufferedImage.getWidth() + ":" + scaledBufferedImage.getHeight();
				} else {
					return storedFile.getId().toString() + ":" + bufferedImage.getWidth() + ":" + bufferedImage.getHeight();
				}
			}
		} else {
			return storedFile.getId().toString();
		}
	}
	
	public String cropVehiclePendingResource(String phoneNumber, String imageId, String type, String crop) throws IOException {
		String cropExp[] = crop.split(":");
		int posX = Integer.parseInt(cropExp[0]);
		int posY = Integer.parseInt(cropExp[1]);
		int size = Integer.parseInt(cropExp[2]);
		logger.info("cropVehiclePendingResource - x:" + posX + " y:" + posY + " size:" + size);
		
		// locate original image
		Query query = new Query();
		Criteria criteria = Criteria.where("_id").is(imageId);
		query.addCriteria(criteria);
		GridFSDBFile file = gridFsTemplate.findOne(query);
		if (null == file) {
			logger.warn("cropVehiclePendingResource - cannot locate image by id: " + imageId);
			return null;
		}
		BufferedImage bufferedImage = Thumbnails.of(file.getInputStream()).sourceRegion(posX, posY, size, size).size(size, size).asBufferedImage();
		if (logger.isDebugEnabled()) {
			logger.debug("processing cropped image width:" + bufferedImage.getWidth() + ", height:" + bufferedImage.getHeight());
		}
		BufferedImage scaledBufferedImage = null;
		// scale image
		if ((bufferedImage.getWidth()>PREFERRED_VEHICLE_IMAGE_SIZE) || (bufferedImage.getHeight()>PREFERRED_VEHICLE_IMAGE_SIZE)) {
			scaledBufferedImage = Thumbnails.of(bufferedImage).size(PREFERRED_VEHICLE_IMAGE_SIZE, PREFERRED_VEHICLE_IMAGE_SIZE).asBufferedImage();
			logger.debug("processing cropped image, scaled width:" + scaledBufferedImage.getWidth() + ", scaled height:" + scaledBufferedImage.getHeight());
		}
		// store image
		GridFSFile storedFile = null;
		if (null != scaledBufferedImage) {
			logger.debug("store scaled image");
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			Thumbnails.of(scaledBufferedImage).scale(1.0).outputFormat("jpg").toOutputStream(outputStream);
			ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
			storedFile = gridFsTemplate.store(inputStream, phoneNumber+type);
		} else {
			logger.debug("store cropped image");
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			Thumbnails.of(bufferedImage).scale(1.0).outputFormat("jpg").toOutputStream(outputStream);
			ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
			storedFile = gridFsTemplate.store(inputStream, phoneNumber+type);
		}
		if (null == storedFile) {
			return null;
		}
		
		// update vehicle pending
		Query pendingQuery = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(VehiclePending.STATUS_UPSERTED);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, statusCriteria);
		pendingQuery.addCriteria(andCriteria);
		Update update = new Update();
		update.set("lastUpdateTime", new Date());
		if (0 == type.compareTo("vehicle")) {
			update.set("vehiclePhotoId", storedFile.getId().toString());
		} else {
			update.set("vehicleInsidePhotoId", storedFile.getId().toString());
		}
		WriteResult result = mongoTemplate.upsert(pendingQuery, update, VehiclePending.class);
		if (1 != result.getN()) {
			return null;
		}
		
		// clean up
		Query deleteQuery = new Query();
		Criteria deleteCriteria = Criteria.where("_id").is(imageId);
		deleteQuery.addCriteria(deleteCriteria);
		gridFsTemplate.delete(deleteQuery);
		
		return storedFile.getId().toString();
	}
	
	public String cropWarehousePendingResource(String phoneNumber, String imageId, String type, String crop) throws IOException {
		String cropExp[] = crop.split(":");
		int posX = Integer.parseInt(cropExp[0]);
		int posY = Integer.parseInt(cropExp[1]);
		int size = Integer.parseInt(cropExp[2]);
		logger.info("cropWarehousePendingResource - x:" + posX + " y:" + posY + " size:" + size);
		
		// locate original image
		Query query = new Query();
		Criteria criteria = Criteria.where("_id").is(imageId);
		query.addCriteria(criteria);
		GridFSDBFile file = gridFsTemplate.findOne(query);
		if (null == file) {
			logger.warn("cropWarehousePendingResource - cannot locate image by id: " + imageId);
			return null;
		}
		BufferedImage bufferedImage = Thumbnails.of(file.getInputStream()).sourceRegion(posX, posY, size, size).size(size, size).asBufferedImage();
		if (logger.isDebugEnabled()) {
			logger.debug("processing cropped image width:" + bufferedImage.getWidth() + ", height:" + bufferedImage.getHeight());
		}
		BufferedImage scaledBufferedImage = null;
		// scale image
		if ((bufferedImage.getWidth()>PREFERRED_WAREHOUSE_IMAGE_SIZE) || (bufferedImage.getHeight()>PREFERRED_WAREHOUSE_IMAGE_SIZE)) {
			scaledBufferedImage = Thumbnails.of(bufferedImage).size(PREFERRED_WAREHOUSE_IMAGE_SIZE, PREFERRED_WAREHOUSE_IMAGE_SIZE).asBufferedImage();
			logger.debug("processing cropped image, scaled width:" + scaledBufferedImage.getWidth() + ", scaled height:" + scaledBufferedImage.getHeight());
		}
		// store image
		GridFSFile storedFile = null;
		if (null != scaledBufferedImage) {
			logger.debug("store scaled image");
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			Thumbnails.of(scaledBufferedImage).scale(1.0).outputFormat("jpg").toOutputStream(outputStream);
			ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
			storedFile = gridFsTemplate.store(inputStream, phoneNumber+type);
		} else {
			logger.debug("store cropped image");
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			Thumbnails.of(bufferedImage).scale(1.0).outputFormat("jpg").toOutputStream(outputStream);
			ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
			storedFile = gridFsTemplate.store(inputStream, phoneNumber+type);
		}
		if (null == storedFile) {
			return null;
		}
		
		// update warehouse pending
		Query pendingQuery = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(WarehousePending.STATUS_UPSERTED);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, statusCriteria);
		pendingQuery.addCriteria(andCriteria);
		Update update = new Update();
		update.set("lastUpdateTime", new Date());
		if (0 == type.compareTo("warehouse")) {
			update.set("warehousePhotoId", storedFile.getId().toString());
		} else {
			update.set("warehouseInsidePhotoId", storedFile.getId().toString());
		}
		WriteResult result = mongoTemplate.upsert(pendingQuery, update, WarehousePending.class);
		if (1 != result.getN()) {
			return null;
		}
		
		// clean up
		Query deleteQuery = new Query();
		Criteria deleteCriteria = Criteria.where("_id").is(imageId);
		deleteQuery.addCriteria(deleteCriteria);
		gridFsTemplate.delete(deleteQuery);
		
		return storedFile.getId().toString();
	}
	
	public String cropProductPendingResource(String phoneNumber, String imageId, String crop) throws IOException {
		String cropExp[] = crop.split(":");
		int posX = Integer.parseInt(cropExp[0]);
		int posY = Integer.parseInt(cropExp[1]);
		int size = Integer.parseInt(cropExp[2]);
		logger.info("cropProductPendingResource - x:" + posX + " y:" + posY + " size:" + size);
		
		// locate original image
		Query query = new Query();
		Criteria criteria = Criteria.where("_id").is(imageId);
		query.addCriteria(criteria);
		GridFSDBFile file = gridFsTemplate.findOne(query);
		if (null == file) {
			logger.warn("cropProductPendingResource - cannot locate image by id: " + imageId);
			return null;
		}
		BufferedImage bufferedImage = Thumbnails.of(file.getInputStream()).sourceRegion(posX, posY, size, size).size(size, size).asBufferedImage();
		if (logger.isDebugEnabled()) {
			logger.debug("processing cropped image width:" + bufferedImage.getWidth() + ", height:" + bufferedImage.getHeight());
		}
		BufferedImage scaledBufferedImage = null;
		// scale image
		if ((bufferedImage.getWidth()>PREFERRED_PRODUCT_IMAGE_SIZE) || (bufferedImage.getHeight()>PREFERRED_PRODUCT_IMAGE_SIZE)) {
			scaledBufferedImage = Thumbnails.of(bufferedImage).size(PREFERRED_PRODUCT_IMAGE_SIZE, PREFERRED_PRODUCT_IMAGE_SIZE).asBufferedImage();
			logger.debug("processing cropped image, scaled width:" + scaledBufferedImage.getWidth() + ", scaled height:" + scaledBufferedImage.getHeight());
		}
		// store image
		GridFSFile storedFile = null;
		if (null != scaledBufferedImage) {
			logger.debug("store scaled image");
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			Thumbnails.of(scaledBufferedImage).scale(1.0).outputFormat("jpg").toOutputStream(outputStream);
			ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
			storedFile = gridFsTemplate.store(inputStream, phoneNumber);
		} else {
			logger.debug("store cropped image");
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			Thumbnails.of(bufferedImage).scale(1.0).outputFormat("jpg").toOutputStream(outputStream);
			ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
			storedFile = gridFsTemplate.store(inputStream, phoneNumber);
		}
		if (null == storedFile) {
			return null;
		}
		
		// update warehouse pending
		Query pendingQuery = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(ProductPending.STATUS_UPSERTED);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, statusCriteria);
		pendingQuery.addCriteria(andCriteria);
		Update update = new Update();
		update.set("lastUpdateTime", new Date());
		update.set("productPhotoId", storedFile.getId().toString());
		WriteResult result = mongoTemplate.upsert(pendingQuery, update, ProductPending.class);
		if (1 != result.getN()) {
			return null;
		}
		
		// clean up
		Query deleteQuery = new Query();
		Criteria deleteCriteria = Criteria.where("_id").is(imageId);
		deleteQuery.addCriteria(deleteCriteria);
		gridFsTemplate.delete(deleteQuery);
		
		return storedFile.getId().toString();
	}
	
	public String addWarehousePendingResource(String phoneNumber, String type, MultipartFile file) throws IOException {
		String filename = phoneNumber + "_" + type;
		WarehouseResourceType resourceType;
		if (0 == type.compareTo("warehouse")) {
			resourceType = WarehouseResourceType.RESOURCE_WAREHOUSE_PHOTO;
		} else {
			resourceType = WarehouseResourceType.RESOURCE_WAREHOUSE_INSIDE_PHOTO;
		}
		
		// remove unlinked image file
		WarehousePending pending = this.findUpsertedWarehousePending(phoneNumber);
		if (null != pending) {
			switch (resourceType) {
				case RESOURCE_WAREHOUSE_PHOTO:
					if (null != pending.getWarehousePhotoId()) {
						Query deleteQuery = new Query();
						Criteria deleteCriteria = Criteria.where("_id").is(pending.getWarehousePhotoId());
						deleteQuery.addCriteria(deleteCriteria);
						gridFsTemplate.delete(deleteQuery);
					}
					break;
				case RESOURCE_WAREHOUSE_INSIDE_PHOTO:
					if (null != pending.getWarehouseInsidePhotoId()) {
						Query deleteQuery = new Query();
						Criteria deleteCriteria = Criteria.where("_id").is(pending.getWarehouseInsidePhotoId());
						deleteQuery.addCriteria(deleteCriteria);
						gridFsTemplate.delete(deleteQuery);
					}
					break;
				default:
					break;
			}
		}
		
		// store the file
		boolean shouldStore = false;
		BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
		if (logger.isDebugEnabled()) {
			logger.debug("processing image <filename:" + filename + ">, width:" + bufferedImage.getWidth() + ", height:" + bufferedImage.getHeight());
		}
		BufferedImage scaledBufferedImage = null;
		if ((resourceType==WarehouseResourceType.RESOURCE_WAREHOUSE_PHOTO) || (resourceType==WarehouseResourceType.RESOURCE_WAREHOUSE_INSIDE_PHOTO)) {
			if (bufferedImage.getWidth() == bufferedImage.getHeight()) {
				// scale image
				if ((bufferedImage.getWidth()>PREFERRED_WAREHOUSE_IMAGE_SIZE) || (bufferedImage.getHeight()>PREFERRED_WAREHOUSE_IMAGE_SIZE)) {
					scaledBufferedImage = Thumbnails.of(bufferedImage).size(PREFERRED_WAREHOUSE_IMAGE_SIZE, PREFERRED_WAREHOUSE_IMAGE_SIZE).asBufferedImage();
					logger.debug("processing image <filename:" + filename + ">, scaled width:" + scaledBufferedImage.getWidth() + ", scaled height:" + scaledBufferedImage.getHeight());
				}
				shouldStore = true;
			} else {
				// resize and store original image and will be cropped by client application later
				if ((bufferedImage.getWidth()>PREFERRED_WAREHOUSE_IMAGE_SIZE) && (bufferedImage.getHeight()>PREFERRED_WAREHOUSE_IMAGE_SIZE)) {
					int scaleFactor;
					if (bufferedImage.getWidth() > bufferedImage.getHeight()) {
						scaleFactor = bufferedImage.getWidth()*PREFERRED_WAREHOUSE_IMAGE_SIZE/bufferedImage.getHeight();
					} else {
						scaleFactor = bufferedImage.getHeight()*PREFERRED_WAREHOUSE_IMAGE_SIZE/bufferedImage.getWidth();
					}
					scaledBufferedImage = Thumbnails.of(bufferedImage).size(scaleFactor, scaleFactor).asBufferedImage();
					logger.debug("resized original image, width:" + scaledBufferedImage.getWidth() + " height:" + scaledBufferedImage.getHeight());
				}
			}
		} else {
			if ((bufferedImage.getWidth()>PREFERRED_WAREHOUSE_IMAGE_SIZE) || (bufferedImage.getHeight()>PREFERRED_WAREHOUSE_IMAGE_SIZE)) {
				scaledBufferedImage = Thumbnails.of(bufferedImage).size(PREFERRED_WAREHOUSE_IMAGE_SIZE, PREFERRED_WAREHOUSE_IMAGE_SIZE).asBufferedImage();
				logger.debug("processing image <filename:" + filename + ">, scaled width:" + scaledBufferedImage.getWidth() + ", scaled height:" + scaledBufferedImage.getHeight());
			}
		}
		GridFSFile storedFile = null;
		if (null != scaledBufferedImage) {
			logger.debug("store scaled image");
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			Thumbnails.of(scaledBufferedImage).scale(1.0).outputFormat("jpg").toOutputStream(outputStream);
			ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
			storedFile = gridFsTemplate.store(inputStream, filename);
		} else {
			logger.debug("store original image");
			storedFile = gridFsTemplate.store(file.getInputStream(), filename);
		}
		if (null == storedFile) {
			return null;
		}
		
		// update warehouse pending
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(WarehousePending.STATUS_UPSERTED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		
		Update update = new Update();
		update.set("lastUpdateTime", new Date());
		switch (resourceType) {
			case RESOURCE_WAREHOUSE_PHOTO:
				if (shouldStore) {
					update.set("warehousePhotoId", storedFile.getId().toString());
				}
				break;
			case RESOURCE_WAREHOUSE_INSIDE_PHOTO:
				if (shouldStore) {
					update.set("warehouseInsidePhotoId", storedFile.getId().toString());
				}
				break;
			default:
				break;
		}
		if (null == pending) {
			update.set("userId", phoneNumber);
			update.set("status", WarehousePending.STATUS_UPSERTED);
		}
		WriteResult result = mongoTemplate.upsert(query, update, WarehousePending.class);
		if (1 != result.getN()) {
			return null;
		}
		
		if ((resourceType==WarehouseResourceType.RESOURCE_WAREHOUSE_PHOTO) || (resourceType==WarehouseResourceType.RESOURCE_WAREHOUSE_INSIDE_PHOTO)) {
			if (bufferedImage.getWidth() == bufferedImage.getHeight()) {
				return storedFile.getId().toString();
			} else {
				if (null != scaledBufferedImage) {
					// resized
					return storedFile.getId().toString() + ":" + scaledBufferedImage.getWidth() + ":" + scaledBufferedImage.getHeight();
				} else {
					return storedFile.getId().toString() + ":" + bufferedImage.getWidth() + ":" + bufferedImage.getHeight();
				}
			}
		} else {
			return storedFile.getId().toString();
		}
	}
	
	public String addProductPendingResource(String phoneNumber, String type, MultipartFile file) throws IOException {
		String filename = phoneNumber + "_" + type;
		
		// remove unlinked image file
		ProductPending pending = this.findUpsertedProductPending(phoneNumber);
		if (null != pending) {
			if (null != pending.getProductPhotoId()) {
				Query deleteQuery = new Query();
				Criteria deleteCriteria = Criteria.where("_id").is(pending.getProductPhotoId());
				deleteQuery.addCriteria(deleteCriteria);
				gridFsTemplate.delete(deleteQuery);
			}
		}
		
		// store the file
		boolean shouldStore = false;
		BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
		if (logger.isDebugEnabled()) {
			logger.debug("processing image <filename:" + filename + ">, width:" + bufferedImage.getWidth() + ", height:" + bufferedImage.getHeight());
		}
		BufferedImage scaledBufferedImage = null;
		if (bufferedImage.getWidth() == bufferedImage.getHeight()) {
			// scale image
			if ((bufferedImage.getWidth()>PREFERRED_PRODUCT_IMAGE_SIZE) || (bufferedImage.getHeight()>PREFERRED_PRODUCT_IMAGE_SIZE)) {
				scaledBufferedImage = Thumbnails.of(bufferedImage).size(PREFERRED_PRODUCT_IMAGE_SIZE, PREFERRED_PRODUCT_IMAGE_SIZE).asBufferedImage();
				logger.debug("processing image <filename:" + filename + ">, scaled width:" + scaledBufferedImage.getWidth() + ", scaled height:" + scaledBufferedImage.getHeight());
			}
			shouldStore = true;
		} else {
			// resize and store original image and will be cropped by client application later
			if ((bufferedImage.getWidth()>PREFERRED_PRODUCT_IMAGE_SIZE) && (bufferedImage.getHeight()>PREFERRED_PRODUCT_IMAGE_SIZE)) {
				int scaleFactor;
				if (bufferedImage.getWidth() > bufferedImage.getHeight()) {
					scaleFactor = bufferedImage.getWidth()*PREFERRED_PRODUCT_IMAGE_SIZE/bufferedImage.getHeight();
				} else {
					scaleFactor = bufferedImage.getHeight()*PREFERRED_PRODUCT_IMAGE_SIZE/bufferedImage.getWidth();
				}
				scaledBufferedImage = Thumbnails.of(bufferedImage).size(scaleFactor, scaleFactor).asBufferedImage();
				logger.debug("resized original image, width:" + scaledBufferedImage.getWidth() + " height:" + scaledBufferedImage.getHeight());
			}
		}
		GridFSFile storedFile = null;
		if (null != scaledBufferedImage) {
			logger.debug("store scaled image");
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			Thumbnails.of(scaledBufferedImage).scale(1.0).outputFormat("jpg").toOutputStream(outputStream);
			ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
			storedFile = gridFsTemplate.store(inputStream, filename);
		} else {
			logger.debug("store original image");
			storedFile = gridFsTemplate.store(file.getInputStream(), filename);
		}
		if (null == storedFile) {
			return null;
		}
		
		// update product pending
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(ProductPending.STATUS_UPSERTED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		
		Update update = new Update();
		update.set("lastUpdateTime", new Date());
		if (shouldStore) {
			update.set("productPhotoId", storedFile.getId().toString());
		}
		if (null == pending) {
			update.set("userId", phoneNumber);
			update.set("status", ProductPending.STATUS_UPSERTED);
		}
		WriteResult result = mongoTemplate.upsert(query, update, ProductPending.class);
		if (1 != result.getN()) {
			return null;
		}
		
		if (bufferedImage.getWidth() == bufferedImage.getHeight()) {
			return storedFile.getId().toString();
		} else {
			if (null != scaledBufferedImage) {
				// resized
				return storedFile.getId().toString() + ":" + scaledBufferedImage.getWidth() + ":" + scaledBufferedImage.getHeight();
			} else {
				return storedFile.getId().toString() + ":" + bufferedImage.getWidth() + ":" + bufferedImage.getHeight();
			}
		}
	}
	
	public GridFSDBFile vehicleResourceById(String imageId) {
		Query query = new Query();
		Criteria criteria = Criteria.where("_id").is(imageId);
		query.addCriteria(criteria);
		
		return gridFsTemplate.findOne(query);
	}
	
	public GridFSDBFile warehouseResourceById(String imageId) {
		Query query = new Query();
		Criteria criteria = Criteria.where("_id").is(imageId);
		query.addCriteria(criteria);
		
		return gridFsTemplate.findOne(query);
	}
	
	public GridFSDBFile productResourceById(String imageId) {
		Query query = new Query();
		Criteria criteria = Criteria.where("_id").is(imageId);
		query.addCriteria(criteria);
		
		return gridFsTemplate.findOne(query);
	}
	
	public List<Map<String, String>> inReviewPendingList(String phoneNumber, Pageable pageable) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(VehiclePending.STATUS_READY_FOR_REVIEW);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		query.with(pageable);
		
		List<Map<String, String>> result = new ArrayList<Map<String, String>>();
		List<VehiclePending> list = mongoTemplate.find(query, VehiclePending.class);
		for (VehiclePending pending : list) {
			result.add(pending.toMap());
		}
		
		return result;
	}
	
	public List<Map<String, Object>> inReviewWarehousePendingList(String phoneNumber, Pageable pageable) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(WarehousePending.STATUS_READY_FOR_REVIEW);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		query.with(pageable);
		
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		List<WarehousePending> list = mongoTemplate.find(query, WarehousePending.class);
		for (WarehousePending pending : list) {
			result.add(pending.toMap());
		}
		
		return result;
	}
	
	public List<Map<String, Object>> inReviewProductPendingList(String phoneNumber, Pageable pageable) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(ProductPending.STATUS_READY_FOR_REVIEW);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		query.with(pageable);
		
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		List<ProductPending> list = mongoTemplate.find(query, ProductPending.class);
		for (ProductPending pending : list) {
			result.add(pending.toMap());
		}
		
		return result;
	}
	
	public List<Map<String, String>> approvedVehicleList(String phoneNumber, Pageable pageable) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(Vehicle.VEHICLE_STATUS_APPROVED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
//		query.with(pageable);
		
		List<Map<String, String>> result = new ArrayList<Map<String, String>>();
		List<Vehicle> list = mongoTemplate.find(query, Vehicle.class);
		for (Vehicle vehicle : list) {
			result.add(vehicle.toMap());
		}
		
		return result;
	}
	
	public List<Map<String, Object>> approvedWarehouseList(String phoneNumber, Pageable pageable) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(Warehouse.WAREHOUSE_STATUS_APPROVED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		query.with(pageable);
		
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		List<Warehouse> list = mongoTemplate.find(query, Warehouse.class);
		for (Warehouse warehouse : list) {
			result.add(warehouse.toMap());
		}
		
		return result;
	}
	
	public List<Map<String, Object>> approvedProductList(String phoneNumber, Pageable pageable) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(Product.PRODUCT_STATUS_APPROVED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		query.with(pageable);
		
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		List<Product> list = mongoTemplate.find(query, Product.class);
		for (Product product : list) {
			result.add(product.toMap());
		}
		
		return result;
	}
	
	public List<Map<String, String>> rejectedVehicleList(String phoneNumber, Pageable pageable) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(Vehicle.VEHICLE_STATUS_REJECTED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		query.with(pageable);
		
		List<Map<String, String>> result = new ArrayList<Map<String, String>>();
		List<Vehicle> list = mongoTemplate.find(query, Vehicle.class);
		for (Vehicle vehicle : list) {
			result.add(vehicle.toMap());
		}
		
		return result;
	}
	
	public List<Map<String, Object>> rejectedWarehouseList(String phoneNumber, Pageable pageable) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(Warehouse.WAREHOUSE_STATUS_REJECTED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		query.with(pageable);
		
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		List<Warehouse> list = mongoTemplate.find(query, Warehouse.class);
		for (Warehouse warehouse : list) {
			result.add(warehouse.toMap());
		}
		
		return result;
	}
	
	public List<Map<String, Object>> rejectedProductList(String phoneNumber, Pageable pageable) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(Product.PRODUCT_STATUS_REJECTED);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, statusCriteria);
		query.addCriteria(criteria);
		query.with(pageable);
		
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		List<Product> list = mongoTemplate.find(query, Product.class);
		for (Product product : list) {
			result.add(product.toMap());
		}
		
		return result;
	}
	
	public void vehicleCounting(String phoneNumber, Map<String, String> map) {
		Query inReviewQuery = new Query();
		Criteria inReviewIdCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria inReviewStatusCriteria = Criteria.where("status").is(VehiclePending.STATUS_READY_FOR_REVIEW);
		Criteria inReviewAndCriteria = new Criteria();
		inReviewAndCriteria.andOperator(inReviewIdCriteria, inReviewStatusCriteria);
		inReviewQuery.addCriteria(inReviewAndCriteria);
		long inReviewCount = mongoTemplate.count(inReviewQuery, VehiclePending.class);
		map.put("inReview", Long.valueOf(inReviewCount).toString());
		
		Query approvedQuery = new Query();
		Criteria approvedIdCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria approvedStatusCriteria = Criteria.where("status").is(Vehicle.VEHICLE_STATUS_APPROVED);
		Criteria approvedAndCriteria = new Criteria();
		approvedAndCriteria.andOperator(approvedIdCriteria, approvedStatusCriteria);
		approvedQuery.addCriteria(approvedAndCriteria);
		long approvedCount = mongoTemplate.count(approvedQuery, Vehicle.class);
		map.put("approved", Long.valueOf(approvedCount).toString());
		
		Query rejectedQuery = new Query();
		Criteria rejectedIdCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria rejectedStatusCriteria = Criteria.where("status").is(Vehicle.VEHICLE_STATUS_REJECTED);
		Criteria rejectedAndCriteria = new Criteria();
		rejectedAndCriteria.andOperator(rejectedIdCriteria, rejectedStatusCriteria);
		rejectedQuery.addCriteria(rejectedAndCriteria);
		long rejectedCount = mongoTemplate.count(rejectedQuery, Vehicle.class);
		map.put("rejected", Long.valueOf(rejectedCount).toString());
	}
	
	public void warehouseCounting(String phoneNumber, Map<String, String> map) {
		Query inReviewQuery = new Query();
		Criteria inReviewIdCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria inReviewStatusCriteria = Criteria.where("status").is(WarehousePending.STATUS_READY_FOR_REVIEW);
		Criteria inReviewAndCriteria = new Criteria();
		inReviewAndCriteria.andOperator(inReviewIdCriteria, inReviewStatusCriteria);
		inReviewQuery.addCriteria(inReviewAndCriteria);
		long inReviewCount = mongoTemplate.count(inReviewQuery, WarehousePending.class);
		map.put("inReview", Long.valueOf(inReviewCount).toString());
		
		Query approvedQuery = new Query();
		Criteria approvedIdCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria approvedStatusCriteria = Criteria.where("status").is(Warehouse.WAREHOUSE_STATUS_APPROVED);
		Criteria approvedAndCriteria = new Criteria();
		approvedAndCriteria.andOperator(approvedIdCriteria, approvedStatusCriteria);
		approvedQuery.addCriteria(approvedAndCriteria);
		long approvedCount = mongoTemplate.count(approvedQuery, Warehouse.class);
		map.put("approved", Long.valueOf(approvedCount).toString());
		
		Query rejectedQuery = new Query();
		Criteria rejectedIdCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria rejectedStatusCriteria = Criteria.where("status").is(Warehouse.WAREHOUSE_STATUS_REJECTED);
		Criteria rejectedAndCriteria = new Criteria();
		rejectedAndCriteria.andOperator(rejectedIdCriteria, rejectedStatusCriteria);
		rejectedQuery.addCriteria(rejectedAndCriteria);
		long rejectedCount = mongoTemplate.count(rejectedQuery, Warehouse.class);
		map.put("rejected", Long.valueOf(rejectedCount).toString());
	}
	
	public void productCounting(String phoneNumber, Map<String, String> map) {
		Query inReviewQuery = new Query();
		Criteria inReviewIdCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria inReviewStatusCriteria = Criteria.where("status").is(ProductPending.STATUS_READY_FOR_REVIEW);
		Criteria inReviewAndCriteria = new Criteria();
		inReviewAndCriteria.andOperator(inReviewIdCriteria, inReviewStatusCriteria);
		inReviewQuery.addCriteria(inReviewAndCriteria);
		long inReviewCount = mongoTemplate.count(inReviewQuery, ProductPending.class);
		map.put("inReview", Long.valueOf(inReviewCount).toString());
		
		Query approvedQuery = new Query();
		Criteria approvedIdCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria approvedStatusCriteria = Criteria.where("status").is(Product.PRODUCT_STATUS_APPROVED);
		Criteria approvedAndCriteria = new Criteria();
		approvedAndCriteria.andOperator(approvedIdCriteria, approvedStatusCriteria);
		approvedQuery.addCriteria(approvedAndCriteria);
		long approvedCount = mongoTemplate.count(approvedQuery, Product.class);
		map.put("approved", Long.valueOf(approvedCount).toString());
		
		Query rejectedQuery = new Query();
		Criteria rejectedIdCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria rejectedStatusCriteria = Criteria.where("status").is(Product.PRODUCT_STATUS_REJECTED);
		Criteria rejectedAndCriteria = new Criteria();
		rejectedAndCriteria.andOperator(rejectedIdCriteria, rejectedStatusCriteria);
		rejectedQuery.addCriteria(rejectedAndCriteria);
		long rejectedCount = mongoTemplate.count(rejectedQuery, Product.class);
		map.put("rejected", Long.valueOf(rejectedCount).toString());
	}
	
	public List<Map<String, Object>> vehicleSpecListing(String phoneNumber, Pageable pageable) {
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		
		Query query = new Query();
		Criteria criteria = Criteria.where("userId").is(phoneNumber);
		query.addCriteria(criteria);
		query.with(pageable);
		
		List<VehicleSpec> list = mongoTemplate.find(query, VehicleSpec.class);
		for (VehicleSpec spec : list) {
			result.add(spec.toMap());
		}
		
		return result;
	}
	
	public List<Map<String, Object>> warehouseSpecListing(String phoneNumber, Pageable pageable) {
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		
		Query query = new Query();
		Criteria criteria = Criteria.where("userId").is(phoneNumber);
		query.addCriteria(criteria);
//		query.with(pageable);
		
		List<WarehouseSpec> list = mongoTemplate.find(query, WarehouseSpec.class);
		for (WarehouseSpec spec : list) {
			result.add(spec.toMap());
		}
		
		return result;
	}
	
	public List<Map<String, Object>> productSpecListing(String phoneNumber, Pageable pageable) {
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria stockCriteria = Criteria.where("stock").is(Integer.valueOf(0));
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, stockCriteria);
		query.addCriteria(criteria);
		
		List<ProductSpec> list = mongoTemplate.find(query, ProductSpec.class);
		for (ProductSpec spec : list) {
			result.add(spec.toMap());
		}
		
		return result;
	}
	
	public VehicleSpec findVehicleSpec(String phoneNumber, String keySpec) {
		Query query = new Query();
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria specCriteria = Criteria.where("keySpec").is(keySpec);
		Criteria criteria = new Criteria();
		criteria.andOperator(userCriteria, specCriteria);
		query.addCriteria(criteria);
		
		return mongoTemplate.findOne(query, VehicleSpec.class);
	}
	
	public List<Map<String, Object>> vehicleServiceListing(String phoneNumber, Pageable pageable) {
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		
		Query query = new Query();
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria stockCriteria = Criteria.where("stock").gt(Integer.valueOf(0));
		Criteria criteria = new Criteria();
		criteria.andOperator(userCriteria, stockCriteria);
		query.addCriteria(criteria);
//		query.with(pageable);
		
		List<VehicleService> list = mongoTemplate.find(query, VehicleService.class);
		for (VehicleService service : list) {
			result.add(service.toMap());
		}
		
		return result;
	}
	
	public List<Map<String, Object>> warehouseServiceListing(String phoneNumber, Pageable pageable) {
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		
		Query query = new Query();
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria stockCriteria = Criteria.where("stock").gt(Integer.valueOf(0));
		Criteria criteria = new Criteria();
		criteria.andOperator(userCriteria, stockCriteria);
		query.addCriteria(criteria);
//		query.with(pageable);
		
		List<WarehouseService> list = mongoTemplate.find(query, WarehouseService.class);
		for (WarehouseService service : list) {
			result.add(service.toMap());
		}
		
		return result;
	}
	
	public List<Map<String, Object>> productServiceListing(String phoneNumber, Pageable pageable) {
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		
		Query query = new Query();
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria stockCriteria = Criteria.where("stock").gt(Integer.valueOf(0));
		Criteria criteria = new Criteria();
		criteria.andOperator(userCriteria, stockCriteria);
		query.addCriteria(criteria);
		
		List<ProductService> list = mongoTemplate.find(query, ProductService.class);
		for (ProductService service : list) {
			result.add(service.toMap());
		}
		
		return result;
	}
	
	public boolean publishCompleteVehicleService(String phoneNumber, PublishCompleteVehicleServiceRequest req) {
		if (req.getRoutes().size() <= 0) {
			logger.error("empty request, ignored");
			return false;
		}
		
		// locate spec
		Query specQuery = new Query();
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria specCriteria = Criteria.where("keySpec").is(req.getKeySpec());
		Criteria criteria = new Criteria();
		criteria.andOperator(userCriteria, specCriteria);
		specQuery.addCriteria(criteria);
		VehicleSpec spec = mongoTemplate.findOne(specQuery, VehicleSpec.class);
		if (null == spec) {
			return false;
		}
		if (spec.getStock().intValue() < req.getStock().intValue()) {
			return false;
		}
		if (spec.getTemperatureSpec().size() <= 0) {
			logger.error("invalid vehicle - empty temperature spec");
			return false;
		}
		
		// locate user
		Query userQuery = new Query();
		Criteria userIdCriteria = Criteria.where("phoneNumber").is(phoneNumber);
		userQuery.addCriteria(userIdCriteria);
		User user = mongoTemplate.findOne(userQuery, User.class);
		if (null == user) {
			return false;
		}
		
		// create vehicle service objects
		// iterate over routes first
		DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
		for (Map<String, String> reqMap : req.getRoutes()) {
			if ((null==reqMap.get("originProvince")) || (null==reqMap.get("originCity")) || (null==reqMap.get("originDistrict")) || (null==reqMap.get("destinationProvince")) ||
					(null==reqMap.get("destinationCity")) || (null==reqMap.get("destinationDistrict")) || (null==reqMap.get("price"))) {
				return false;
			}
			// iterate temperature spec second
			for (String temperature : spec.getTemperatureSpec()) {
				VehicleService service = new VehicleService();
				service.setUserId(phoneNumber);
				service.setKeySpec(spec.getKeySpec());
				service.setLengthSpec(spec.getLengthSpec());
				service.setTemperatureSpec(temperature);
				service.setFullLoadWeight(spec.getFullLoadWeight());
				service.setFullLoadMass(spec.getFullLoadMass());
				if (null != spec.getIndividual()) {
					service.setIndividual(spec.getIndividual());
				}
				if (null != spec.getEnterprise()) {
					service.setEnterprise(spec.getEnterprise());
				}
				try {
					service.setStartDate(formatter.parse(req.getStartDate()));
					service.setUntilDate(formatter.parse(req.getUntilDate()));
				} catch (ParseException e) {
					return false;
				}
				if (null != spec.getDeviceSN()) {
					service.setDeviceSN(spec.getDeviceSN());
				}
				service.setTemperatureSpecList(spec.getTemperatureSpec());
				service.setOriginProvince(reqMap.get("originProvince"));
				service.setOriginCity(reqMap.get("originCity"));
				service.setOriginDistrict(reqMap.get("originDistrict"));
				service.setDestinationProvince(reqMap.get("destinationProvince"));
				service.setDestinationCity(reqMap.get("destinationCity"));
				service.setDestinationDistrict(reqMap.get("destinationDistrict"));
				service.setPrice(Float.valueOf(reqMap.get("price")));
				service.setStock(req.getStock());
				service.setManInChargeName(spec.getManInChargeName());
				service.setManInChargePhoneNumber(spec.getManInChargePhoneNumber());
				service.setUserHash(user.getId());
				service.setLastUpdateTime(new Date());
				service.setCreationDate(LocalDateTime.now());
				mongoTemplate.save(service);
				
				// publish only one service identified by the spec
				break;
			}
		}
		
		// update spec stock
		Update specUpdate = new Update();
		specUpdate.inc("stock", Integer.valueOf(req.getStock().intValue()*-1));
		WriteResult result = mongoTemplate.updateFirst(specQuery, specUpdate, VehicleSpec.class);
		if (!result.isUpdateOfExisting()) {
			return false;
		}
		
		return true;
	}
	
	public boolean publishWarehouseService(String phoneNumber, PublishWarehouseServiceRequest req) {
		// locate spec
		Query specQuery = new Query();
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria specCriteria = Criteria.where("keySpec").is(req.getKeySpec());
		Criteria criteria = new Criteria();
		criteria.andOperator(userCriteria, specCriteria);
		specQuery.addCriteria(criteria);
		WarehouseSpec spec = mongoTemplate.findOne(specQuery, WarehouseSpec.class);
		if (null == spec) {
			return false;
		}
		if (spec.getStock().intValue() < req.getStock().intValue()) {
			return false;
		}
		
		// locate user
		Query userQuery = new Query();
		Criteria userIdCriteria = Criteria.where("phoneNumber").is(phoneNumber);
		userQuery.addCriteria(userIdCriteria);
		User user = mongoTemplate.findOne(userQuery, User.class);
		if (null == user) {
			return false;
		}
		
		// create warehouse service objects
		DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
		WarehouseService service = new WarehouseService();
		service.setUserId(phoneNumber);
		service.setName(spec.getName());
		service.setKeySpec(req.getKeySpec());
		service.setTemperatureSpec(spec.getTemperatureSpec());
		service.setProvince(spec.getProvince());
		service.setCity(spec.getCity());
		service.setDistrict(spec.getDistrict());
		service.setAddress(spec.getAddress());
		service.setStock(req.getStock());
		service.setStockTotal(spec.getStockTotal());
		service.setPrice(req.getPrice());
		service.setWarehousePhotoId(spec.getWarehousePhotoId());
		service.setUserHash(user.getId());
		if (null != spec.getDeviceSN()) {
			service.setDeviceSN(spec.getDeviceSN());
		}
		if (null != spec.getIndividual()) {
			service.setIndividual(spec.getIndividual());
		}
		if (null != spec.getEnterprise()) {
			service.setEnterprise(spec.getEnterprise());
		}
		service.setManInChargeName(spec.getManInChargeName());
		service.setManInChargePhoneNumber(spec.getManInChargePhoneNumber());
		try {
			service.setStartDate(formatter.parse(req.getStartDate()));
			service.setUntilDate(formatter.parse(req.getUntilDate()));
		} catch (ParseException e) {
			return false;
		}
		service.setCreationDate(LocalDateTime.now());
		mongoTemplate.save(service);
		
		// update spec stock
		Update specUpdate = new Update();
		specUpdate.inc("stock", Integer.valueOf(req.getStock().intValue()*-1));
		WriteResult result = mongoTemplate.updateFirst(specQuery, specUpdate, WarehouseSpec.class);
		if (!result.isUpdateOfExisting()) {
			return false;
		}
		
		return true;
	}
	
	public boolean publishProductService(String phoneNumber, PublishProductServiceRequest req) {
		// locate and update spec
		Query specQuery = new Query();
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria specCriteria = Criteria.where("keySpec").is(req.getKeySpec());
		Criteria stockCriteria = Criteria.where("stock").is(Integer.valueOf(0));
		Criteria criteria = new Criteria();
		criteria.andOperator(userCriteria, specCriteria, stockCriteria);
		specQuery.addCriteria(criteria);
		ProductSpec spec = mongoTemplate.findOne(specQuery, ProductSpec.class);
		if (null == spec) {
			return false;
		}
		Update specUpdate = new Update();
		specUpdate.inc("stock", req.getStock());
		WriteResult result = mongoTemplate.updateFirst(specQuery, specUpdate, ProductSpec.class);
		if (!result.isUpdateOfExisting()) {
			return false;
		}
		
		// locate user
		Query userQuery = new Query();
		Criteria userIdCriteria = Criteria.where("phoneNumber").is(phoneNumber);
		userQuery.addCriteria(userIdCriteria);
		User user = mongoTemplate.findOne(userQuery, User.class);
		if (null == user) {
			return false;
		}
		
		// create product service object
		DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
		ProductService service = new ProductService();
		service.setUserId(phoneNumber);
		service.setKeySpec(req.getKeySpec());
		service.setName(spec.getName());
		service.setCategory(spec.getCategory());
		service.setProvince(spec.getProvince());
		service.setCity(spec.getCity());
		service.setDistrict(spec.getDistrict());
		service.setOriginProvince(spec.getOriginProvince());
		service.setOriginCity(spec.getOriginCity());
		service.setOriginDistrict(spec.getOriginDistrict());
		service.setProductPhotoId(spec.getProductPhotoId());
		service.setTitle(req.getTitle());
		service.setDescription(req.getDescription());
		service.setUserHash(user.getId());
		service.setManInChargeName(spec.getManInChargeName());
		service.setManInChargePhoneNumber(spec.getManInChargePhoneNumber());
		service.setMinimumVolume(req.getMinimumVolume());
		if (null != spec.getIndividual()) {
			service.setIndividual(spec.getIndividual());
		}
		if (null != spec.getEnterprise()) {
			service.setEnterprise(spec.getEnterprise());
		}
		try {
			service.setAvailableStart(formatter.parse(req.getAvailableStart()));
			service.setAvailableUntil(formatter.parse(req.getAvailableUntil()));
		} catch (ParseException e) {
			return false;
		}
		service.setPrice(req.getPrice());
		service.setStock(req.getStock());
		service.setCreationDate(LocalDateTime.now());
		mongoTemplate.save(service);
		
		return true;
	}
	
	public boolean cancelCompleteVehicleService(String phoneNumber, CancelCompleteVehicleServiceRequest req) {
		// reduce service stock
		Query serviceQuery = new Query();
		Criteria serviceOwnerCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria serviceSpecCriteria = Criteria.where("keySpec").is(req.getKeySpec());
		DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
		Date startDate, untilDate;
		try {
			startDate = formatter.parse(req.getStartDate());
			untilDate = formatter.parse(req.getUntilDate());
		} catch (ParseException e) {
			return false;
		}
		Criteria serviceStartDateCriteria = Criteria.where("startDate").is(startDate);
		Criteria serviceUntilDateCriteria = Criteria.where("untilDate").is(untilDate);
		Criteria serviceStockCriteria = Criteria.where("stock").is(req.getStock());
		Criteria serviceCriteria = new Criteria();
		serviceCriteria.andOperator(serviceOwnerCriteria, serviceSpecCriteria, serviceStartDateCriteria, serviceUntilDateCriteria, serviceStockCriteria);
		serviceQuery.addCriteria(serviceCriteria);
		Update serviceUpdate = new Update();
		serviceUpdate.inc("stock", Integer.valueOf(req.getStock().intValue()*-1));
		WriteResult serviceReductionResult = mongoTemplate.updateMulti(serviceQuery, serviceUpdate, VehicleService.class);
		if (serviceReductionResult.getN() <= 0) {
			if (logger.isWarnEnabled()) {
				logger.warn("cancelCompleteVehicleService - service stock not equal, maybe new orders arrives");
			}
			return false;
		}
		
		// try increase stock
		Query stockQuery = new Query();
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria keySpecCriteria = Criteria.where("keySpec").is(req.getKeySpec());
		Criteria increaseCriteria = new Criteria();
		increaseCriteria.andOperator(userCriteria, keySpecCriteria);
		stockQuery.addCriteria(increaseCriteria);
		Update stockUpdate = new Update();
		stockUpdate.inc("stock", Integer.valueOf(req.getStock().intValue()));
		WriteResult stockReductionResult = mongoTemplate.updateFirst(stockQuery, stockUpdate, VehicleSpec.class);
		if (!stockReductionResult.isUpdateOfExisting()) {
			if (logger.isWarnEnabled()) {
				logger.warn("cancelCompleteVehicleService - unable to increase stock for " + req.getStock().toString());
			}
			return false;
		}
		
		return true;
	}
	
	public boolean cancelCompleteVehicleService(String serviceId) {
		Query serviceQuery = new Query();
		Criteria serviceIdCriteria = Criteria.where("id").is(serviceId);
		serviceQuery.addCriteria(serviceIdCriteria);
		VehicleService service = mongoTemplate.findOne(serviceQuery, VehicleService.class);
		if (null == service) {
			logger.warn("cancelCompleteVehicleService - failed to locate service object by id: " + serviceId);
			return false;
		}
		if (service.getStock().intValue() <= 0) {
			logger.info("cancelCompleteVehicleService - zero stock, probably got canceled already, id: " + serviceId);
			return true;
		}
		// reduce service stock
		Query serviceReductionQuery = new Query();
		Criteria serviceOwnerCriteria = Criteria.where("userId").is(service.getUserId());
		Criteria serviceSpecCriteria = Criteria.where("keySpec").is(service.getKeySpec());
		Criteria serviceStartDateCriteria = Criteria.where("startDate").is(service.getStartDate());
		Criteria serviceUntilDateCriteria = Criteria.where("untilDate").is(service.getUntilDate());
		Criteria serviceStockCriteria = Criteria.where("stock").is(service.getStock());
		Criteria serviceCriteria = new Criteria();
		serviceCriteria.andOperator(serviceOwnerCriteria, serviceSpecCriteria, serviceStartDateCriteria, serviceUntilDateCriteria, serviceStockCriteria);
		serviceReductionQuery.addCriteria(serviceCriteria);
		Update serviceUpdate = new Update();
		serviceUpdate.inc("stock", Integer.valueOf(service.getStock().intValue()*-1));
		WriteResult serviceReductionResult = mongoTemplate.updateMulti(serviceReductionQuery, serviceUpdate, VehicleService.class);
		if (!serviceReductionResult.isUpdateOfExisting()) {
			logger.error("cancelCompleteVehicleService - failed to update service object by id: " + serviceId);
			return false;
		}
		
		// try increase stock
		Query stockQuery = new Query();
		Criteria userCriteria = Criteria.where("userId").is(service.getUserId());
		Criteria keySpecCriteria = Criteria.where("keySpec").is(service.getKeySpec());
		Criteria increaseCriteria = new Criteria();
		increaseCriteria.andOperator(userCriteria, keySpecCriteria);
		stockQuery.addCriteria(increaseCriteria);
		Update stockUpdate = new Update();
		stockUpdate.inc("stock", Integer.valueOf(service.getStock().intValue()));
		WriteResult stockReductionResult = mongoTemplate.updateFirst(stockQuery, stockUpdate, VehicleSpec.class);
		if (!stockReductionResult.isUpdateOfExisting()) {
			logger.error("cancelCompleteVehicleService - failed to update service spec by service object's id: " + serviceId);
			return false;
		}
		
		return true;
	}
	
	public boolean cancelWarehouseService(String phoneNumber, CancelWarehouseServiceRequest req) {
		// reduce service stock
		Query serviceQuery = new Query();
		Criteria serviceOwnerCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria serviceSpecCriteria = Criteria.where("keySpec").is(req.getKeySpec());
		DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
		Date startDate, untilDate;
		try {
			startDate = formatter.parse(req.getStartDate());
			untilDate = formatter.parse(req.getUntilDate());
		} catch (ParseException e) {
			return false;
		}
		Criteria serviceStartDateCriteria = Criteria.where("startDate").is(startDate);
		Criteria serviceUntilDateCriteria = Criteria.where("untilDate").is(untilDate);
		Criteria serviceStockCriteria = Criteria.where("stock").is(req.getStock());
		Criteria servicePriceCriteria = Criteria.where("price").is(req.getPrice());
		Criteria serviceCriteria = new Criteria();
		serviceCriteria.andOperator(serviceOwnerCriteria, serviceSpecCriteria, serviceStartDateCriteria, serviceUntilDateCriteria, serviceStockCriteria, servicePriceCriteria);
		serviceQuery.addCriteria(serviceCriteria);
		Update serviceUpdate = new Update();
		serviceUpdate.inc("stock", Integer.valueOf(req.getStock().intValue()*-1));
		WriteResult serviceReductionResult = mongoTemplate.updateFirst(serviceQuery, serviceUpdate, WarehouseService.class);
		if (!serviceReductionResult.isUpdateOfExisting()) {
			if (logger.isWarnEnabled()) {
				logger.warn("cancelWarehouseService - service stock not equal, maybe new orders arrives");
			}
			return false;
		}
		
		// try increase stock
		Query stockQuery = new Query();
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria keySpecCriteria = Criteria.where("keySpec").is(req.getKeySpec());
		Criteria increaseCriteria = new Criteria();
		increaseCriteria.andOperator(userCriteria, keySpecCriteria);
		stockQuery.addCriteria(increaseCriteria);
		Update stockUpdate = new Update();
		stockUpdate.inc("stock", Integer.valueOf(req.getStock().intValue()));
		WriteResult stockReductionResult = mongoTemplate.updateFirst(stockQuery, stockUpdate, WarehouseSpec.class);
		if (!stockReductionResult.isUpdateOfExisting()) {
			if (logger.isWarnEnabled()) {
				logger.warn("cancelWarehouseService - unable to increase stock for " + req.getStock().toString());
			}
			return false;
		}
		
		return true;
	}
	
	public boolean cancelWarehouseService(String serviceId) {
		Query serviceQuery = new Query();
		Criteria serviceIdCriteria = Criteria.where("id").is(serviceId);
		serviceQuery.addCriteria(serviceIdCriteria);
		WarehouseService service = mongoTemplate.findOne(serviceQuery, WarehouseService.class);
		if (null == service) {
			logger.warn("cancelWarehouseService - failed to locate service object by id: " + serviceId);
			return false;
		}
		Update serviceUpdate = new Update();
		serviceUpdate.inc("stock", Integer.valueOf(service.getStock().intValue()*-1));
		WriteResult serviceReductionResult = mongoTemplate.updateFirst(serviceQuery, serviceUpdate, WarehouseService.class);
		if (!serviceReductionResult.isUpdateOfExisting()) {
			if (logger.isWarnEnabled()) {
				logger.warn("cancelWarehouseService - failed to update service object by id: " + serviceId);
			}
			return false;
		}
		
		// try increase stock
		Query stockQuery = new Query();
		Criteria userCriteria = Criteria.where("userId").is(service.getUserId());
		Criteria keySpecCriteria = Criteria.where("keySpec").is(service.getKeySpec());
		Criteria increaseCriteria = new Criteria();
		increaseCriteria.andOperator(userCriteria, keySpecCriteria);
		stockQuery.addCriteria(increaseCriteria);
		Update stockUpdate = new Update();
		stockUpdate.inc("stock", Integer.valueOf(service.getStock().intValue()));
		WriteResult stockReductionResult = mongoTemplate.updateFirst(stockQuery, stockUpdate, WarehouseSpec.class);
		if (!stockReductionResult.isUpdateOfExisting()) {
			logger.warn("cancelWarehouseService - failed to update service spec by service object's id: " + serviceId);
			return false;
		}
		
		return true;
	}
	
	public boolean cancelProductService(String phoneNumber, CancelProductServiceRequest req) {
		// cancel service
		Query serviceQuery = new Query();
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria keySpecCriteria = Criteria.where("keySpec").is(req.getKeySpec());
		Criteria serviceCriteria = new Criteria();
		serviceCriteria.andOperator(userCriteria, keySpecCriteria);
		serviceQuery.addCriteria(serviceCriteria);
		Update serviceUpdate = new Update();
		serviceUpdate.set("stock", Integer.valueOf(0));
		WriteResult serviceResult = mongoTemplate.updateMulti(serviceQuery, serviceUpdate, ProductService.class);
		if (!serviceResult.isUpdateOfExisting()) {
			if (logger.isWarnEnabled()) {
				logger.warn("cancelProductService - failed to update service object");
			}
			return false;
		}
		
		// reset spec
		Query specQuery = new Query();
		Criteria userSpecCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria keySpecSpecCriteria = Criteria.where("keySpec").is(req.getKeySpec());
		Criteria specCriteria = new Criteria();
		specCriteria.andOperator(userSpecCriteria, keySpecSpecCriteria);
		specQuery.addCriteria(specCriteria);
		Update specUpdate = new Update();
		specUpdate.set("stock", Integer.valueOf(0));
		WriteResult specResult = mongoTemplate.updateFirst(specQuery, specUpdate, ProductSpec.class);
		if (!specResult.isUpdateOfExisting()) {
			if (logger.isWarnEnabled()) {
				logger.warn("cancelProductService - failed to update service spec");
			}
			return false;
		}
		
		return true;
	}
	
	public boolean cancelProductService(String serviceId) {
		// cancel service
		Query serviceQuery = new Query();
		Criteria idCriteria = Criteria.where("id").is(serviceId);
		serviceQuery.addCriteria(idCriteria);
		ProductService service = mongoTemplate.findOne(serviceQuery, ProductService.class);
		if (null == service) {
			logger.warn("cancelProductService - failed to locate service object by id: " + serviceId);
			return false;
		}
		Update serviceUpdate = new Update();
		serviceUpdate.set("stock", Integer.valueOf(0));
		WriteResult serviceResult = mongoTemplate.updateFirst(serviceQuery, serviceUpdate, ProductService.class);
		if (!serviceResult.isUpdateOfExisting()) {
			logger.warn("cancelProductService - failed to update service object by id: " + serviceId);
			return false;
		}
		
		// reset spec
		Query specQuery = new Query();
		Criteria userSpecCriteria = Criteria.where("userId").is(service.getUserId());
		Criteria keySpecCriteria = Criteria.where("keySpec").is(service.getKeySpec());
		Criteria specCriteria = new Criteria();
		specCriteria.andOperator(userSpecCriteria, keySpecCriteria);
		specQuery.addCriteria(specCriteria);
		Update specUpdate = new Update();
		specUpdate.set("stock", Integer.valueOf(0));
		WriteResult specResult = mongoTemplate.updateFirst(specQuery, specUpdate, ProductSpec.class);
		if (!specResult.isUpdateOfExisting()) {
			logger.warn("cancelProductService - failed to update service spec by service object's id: " + serviceId);
			return false;
		}
		
		return true;
	}
	
	public Map<String, String> vehicleInfo(String phoneNumber, String vehicleId) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("id").is(vehicleId);
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, userCriteria);
		query.addCriteria(andCriteria);
		
		Vehicle vehicle = mongoTemplate.findOne(query, Vehicle.class);
		if (null == vehicle) {
			return new HashMap<String, String>();
		} else {
			return vehicle.toMap();
		}
	}
	
	public Map<String, Object> warehouseInfo(String phoneNumber, String warehouseId) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria warehouseIdCriteria = Criteria.where("id").is(warehouseId);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, warehouseIdCriteria);
		query.addCriteria(criteria);
		
		Warehouse warehouse = mongoTemplate.findOne(query, Warehouse.class);
		if (null == warehouse) {
			return new HashMap<String, Object>();
		} else {
			return warehouse.toMap();
		}
	}
	
	public Map<String, Object> productInfo(String phoneNumber, String productId) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria productIdCriteria = Criteria.where("id").is(productId);
		Criteria criteria = new Criteria();
		criteria.andOperator(idCriteria, productIdCriteria);
		query.addCriteria(criteria);
		
		Product product = mongoTemplate.findOne(query, Product.class);
		if (null == product) {
			return new HashMap<String, Object>();
		} else {
			return product.toMap();
		}
	}
	
	public void addDriver(String phoneNumber, AddDriverRequest req) {
		Driver driver = new Driver();
		driver.setUserId(phoneNumber);
		driver.setName(req.getName());
		driver.setPhoneNumber(req.getPhoneNumber());
		driver.setGender(req.getGender());
		driver.setDrivingAge(req.getDrivingAge());
		driver.setLastUpdateTime(new Date());
		if (null != req.getDrivingLicensePhotoId()) {
			driver.setDrivingLicensePhotoId(req.getDrivingLicensePhotoId());
		}
		
		mongoTemplate.save(driver);
	}
	
	public boolean deleteRejectedVehicle(String phoneNumber, String vehicleId) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("id").is(vehicleId);
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(Vehicle.VEHICLE_STATUS_REJECTED);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, userCriteria, statusCriteria);
		query.addCriteria(andCriteria);
		
		WriteResult result = mongoTemplate.remove(query, Vehicle.class);
		if (1 != result.getN()) {
			return false;
		}
		
		return true;
	}
	
	public boolean deleteRejectedWarehouse(String phoneNumber, String warehouseId) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("id").is(warehouseId);
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(Warehouse.WAREHOUSE_STATUS_REJECTED);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, userCriteria, statusCriteria);
		query.addCriteria(andCriteria);
		
		WriteResult result = mongoTemplate.remove(query, Warehouse.class);
		if (1 != result.getN()) {
			return false;
		}
		
		return true;
	}
	
	public boolean deleteRejectedProduct(String phoneNumber, String productId) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("id").is(productId);
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(Product.PRODUCT_STATUS_REJECTED);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, userCriteria, statusCriteria);
		query.addCriteria(andCriteria);
		
		WriteResult result = mongoTemplate.remove(query, Product.class);
		if (1 != result.getN()) {
			return false;
		}
		
		return true;
	}
	
	public boolean deleteApprovedVehicle(String phoneNumber, String vehicleId) {
		Query vehicleQuery = new Query();
		Criteria vehicleIdCriteria = Criteria.where("id").is(vehicleId);
		vehicleQuery.addCriteria(vehicleIdCriteria);
		Vehicle vehicle = mongoTemplate.findOne(vehicleQuery, Vehicle.class);
		if (null == vehicle) {
			return false;
		}
		
		// locate vehicle spec
		Query specQuery = new Query();
		Criteria specUserCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria specVehicleCriteria = Criteria.where("vehicles").all(vehicle.getLicensePlate());
		Criteria specAndCriteria = new Criteria();
		specAndCriteria.andOperator(specUserCriteria, specVehicleCriteria);
		specQuery.addCriteria(specAndCriteria);
		VehicleSpec spec = mongoTemplate.findOne(specQuery, VehicleSpec.class);
		if (null == spec) {
			return false;
		}
		if (1 >= spec.getStock().intValue()) {
			mongoTemplate.remove(spec);
		} else {
			List<String> vehicles = spec.getVehicles();
			vehicles.remove(vehicleId);
			Update specUpdate = new Update();
			specUpdate.set("vehicles", vehicles);
			specUpdate.inc("stock", Integer.valueOf(-1));
			mongoTemplate.updateFirst(specQuery, specUpdate, VehicleSpec.class);
		}
		
		// delete vehicle
		mongoTemplate.remove(vehicle);
		
		return true;
	}
	
	public boolean deleteApprovedWarehouse(String phoneNumber, String warehouseId) {
		Query warehouseQuery = new Query();
		Criteria warehouseIdCriteria = Criteria.where("id").is(warehouseId);
		warehouseQuery.addCriteria(warehouseIdCriteria);
		Warehouse warehouse = mongoTemplate.findOne(warehouseQuery, Warehouse.class);
		if (null == warehouse) {
			return false;
		}
		
		// delete warehouse spec to make this warehouse invisible to user
		Query specQuery = new Query();
		Criteria specUserCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria specNameCriteria = Criteria.where("name").is(warehouse.getName());
		Criteria specAndCriteria = new Criteria();
		specAndCriteria.andOperator(specUserCriteria, specNameCriteria);
		specQuery.addCriteria(specAndCriteria);
		mongoTemplate.remove(specQuery, WarehouseSpec.class);
		
		// delete warehouse itself
		mongoTemplate.remove(warehouse);
		
		return true;
	}
	
	public boolean deleteApprovedProduct(String phoneNumber, String productId) {
		Query productQuery = new Query();
		Criteria productIdCriteria = Criteria.where("id").is(productId);
		productQuery.addCriteria(productIdCriteria);
		Product product = mongoTemplate.findOne(productQuery, Product.class);
		if (null == product) {
			return false;
		}
		
		// delete product spec to make this product invisible to user
		Query specQuery = new Query();
		Criteria specUserCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria specNameCriteria = Criteria.where("name").is(product.getName());
		Criteria specAndCriteria = new Criteria();
		specAndCriteria.andOperator(specUserCriteria, specNameCriteria);
		specQuery.addCriteria(specAndCriteria);
		mongoTemplate.remove(specQuery, ProductSpec.class);
		
		// delete product itself
		mongoTemplate.remove(product);
		
		return true;
	}
	
	public boolean updateVehicleInfo(String phoneNumber, UpdateVehicleInfoRequest req) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("id").is(req.getVehicleId());
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(Vehicle.VEHICLE_STATUS_APPROVED);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, userCriteria, statusCriteria);
		query.addCriteria(andCriteria);
		
		Update update = new Update();
		update.set("manInChargeName", req.getManInChargeName());
		update.set("manInChargePhoneNumber", req.getManInChargePhoneNumber());
		update.set("owner", req.getOwner());
		update.set("odometer", req.getOdometer());
		
		WriteResult result = mongoTemplate.updateFirst(query, update, Vehicle.class);
		if (!result.isUpdateOfExisting()) {
			return false;
		}
		
		return true;
	}
	
	public boolean updateWarehouseInfo(String phoneNumber, UpdateWarehouseInfoRequest req) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("id").is(req.getWarehouseId());
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(Warehouse.WAREHOUSE_STATUS_APPROVED);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, userCriteria, statusCriteria);
		query.addCriteria(andCriteria);
		
		Update update = new Update();
		update.set("manInChargeName", req.getManInChargeName());
		update.set("manInChargePhoneNumber", req.getManInChargePhoneNumber());
		
		WriteResult result = mongoTemplate.updateFirst(query, update, Warehouse.class);
		if (!result.isUpdateOfExisting()) {
			return false;
		}
		
		return true;
	}
	
	public boolean updateProductInfo(String phoneNumber, UpdateProductInfoRequest req) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("id").is(req.getProductId());
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria statusCriteria = Criteria.where("status").is(Product.PRODUCT_STATUS_APPROVED);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, userCriteria, statusCriteria);
		query.addCriteria(andCriteria);
		
		Update update = new Update();
		update.set("manInChargeName", req.getManInChargeName());
		update.set("manInChargePhoneNumber", req.getManInChargePhoneNumber());
		
		WriteResult result = mongoTemplate.updateFirst(query, update, Product.class);
		if (!result.isUpdateOfExisting()) {
			return false;
		}
		
		return true;
	}
	
	public String addDriverResource(String phoneNumber, String type, MultipartFile file) throws IOException {
		String filename = phoneNumber + "_" + type;
		
		// store the file
		BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
		if (logger.isDebugEnabled()) {
			logger.debug("processing image <filename:" + filename + ">, width:" + bufferedImage.getWidth() + ", height:" + bufferedImage.getHeight());
		}
		BufferedImage scaledBufferedImage = null;
		if ((bufferedImage.getWidth()>PREFERRED_VEHICLE_IMAGE_SIZE) || (bufferedImage.getHeight()>PREFERRED_VEHICLE_IMAGE_SIZE)) {
			scaledBufferedImage = Thumbnails.of(bufferedImage).size(PREFERRED_VEHICLE_IMAGE_SIZE, PREFERRED_VEHICLE_IMAGE_SIZE).asBufferedImage();
			logger.debug("processing image <filename:" + filename + ">, scaled width:" + scaledBufferedImage.getWidth() + ", scaled height:" + scaledBufferedImage.getHeight());
		}
		GridFSFile storedFile = null;
		if (null != scaledBufferedImage) {
			logger.debug("store scaled image");
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			Thumbnails.of(scaledBufferedImage).scale(1.0).outputFormat("jpg").toOutputStream(outputStream);
			ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
			storedFile = gridFsTemplate.store(inputStream, filename);
		} else {
			logger.debug("store original image");
			storedFile = gridFsTemplate.store(file.getInputStream(), filename);
		}
		if (null == storedFile) {
			return null;
		}
		
		return storedFile.getId().toString();
	}
	
	public GridFSDBFile driverResource(String imageId) {
		Query query = new Query();
		Criteria criteria = Criteria.where("_id").is(imageId);
		query.addCriteria(criteria);
		
		return gridFsTemplate.findOne(query);
	}
	
	public List<Map<String, String>> driverListing(String phoneNumber, Pageable pageable) {
		Query query = new Query();
		Criteria criteria = Criteria.where("userId").is(phoneNumber);
		query.addCriteria(criteria);
		query.with(pageable);
		
		List<Map<String, String>> result = new ArrayList<Map<String, String>>();
		List<Driver> list = mongoTemplate.find(query, Driver.class);
		for (Driver driver : list) {
			result.add(driver.toMap());
		}
		
		return result;
	}
	
	public boolean updateDriver(String phoneNumber, UpdateDriverRequest req) {
		Query query = new Query();
		// for caution
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria idCriteria = Criteria.where("id").is(req.getId());
		Criteria criteria = new Criteria();
		criteria.andOperator(userCriteria, idCriteria);
		query.addCriteria(criteria);
		
		Update update = new Update();
		update.set("lastUpdateTime", new Date());
		if (null != req.getName()) {
			update.set("name", req.getName());
		}
		if (null != req.getPhoneNumber()) {
			update.set("phoneNumber", req.getPhoneNumber());
		}
		if (null != req.getGender()) {
			update.set("gender", req.getGender());
		}
		if (null != req.getDrivingAge()) {
			update.set("drivingAge", req.getDrivingAge());
		}
		if (null != req.getDrivingLicensePhotoId()) {
			update.set("drivingLicensePhotoId", req.getDrivingLicensePhotoId());
			
			Driver driver = mongoTemplate.findOne(query, Driver.class);
			if ((null!=driver) && (null!=driver.getDrivingLicensePhotoId()) && (0!=driver.getDrivingLicensePhotoId().compareTo(req.getDrivingLicensePhotoId()))) {
				// unlink previous photo
				Query deleteQuery = new Query();
				Criteria deleteCriteria = Criteria.where("_id").is(driver.getDrivingLicensePhotoId());
				deleteQuery.addCriteria(deleteCriteria);
				gridFsTemplate.delete(deleteQuery);
			}
		}
		
		WriteResult result = mongoTemplate.updateFirst(query, update, Driver.class);
		if (!result.isUpdateOfExisting()) {
			return false;
		}
		
		return true;
	}
	
	public boolean deleteDriver(String phoneNumber, String driverId) {
		Query query = new Query();
		// for caution
		Criteria userCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria idCriteria = Criteria.where("id").is(driverId);
		Criteria criteria = new Criteria();
		criteria.andOperator(userCriteria, idCriteria);
		query.addCriteria(criteria);
		
		// locate driver to unlink photo as necessary
		Driver driver = mongoTemplate.findOne(query, Driver.class);
		if (null == driver) {
			return false;
		}
		if (null != driver.getDrivingLicensePhotoId()) {
			Query deleteQuery = new Query();
			Criteria deleteCriteria = Criteria.where("_id").is(driver.getDrivingLicensePhotoId());
			deleteQuery.addCriteria(deleteCriteria);
			gridFsTemplate.delete(deleteQuery);
		}
		
		// remove driver now
		WriteResult result = mongoTemplate.remove(query, Driver.class);
		if (1 != result.getN()) {
			return false;
		}
		
		return true;
	}
	
	public List<Map<String, Object>> searchCompleteVehicleService(SearchCompleteVehicleServiceRequest req, Pageable pageable) {
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		
		Query query = new Query();
		Criteria andCriteria = new Criteria();
		List<Criteria> criteriaList = new ArrayList<Criteria>();
		Criteria stockCriteria = Criteria.where("stock").gt(Integer.valueOf(0));
		criteriaList.add(stockCriteria);
		if (null != req.getOriginProvince()) {
			Criteria criteria = Criteria.where("originProvince").is(req.getOriginProvince());
			criteriaList.add(criteria);
		}
		if (null != req.getOriginCity()) {
			Criteria criteria = Criteria.where("originCity").is(req.getOriginCity());
			criteriaList.add(criteria);
		}
		if (null != req.getDestinationProvince()) {
			Criteria criteria = Criteria.where("destinationProvince").is(req.getDestinationProvince());
			criteriaList.add(criteria);
		}
		if (null != req.getDestinationCity()) {
			Criteria criteria = Criteria.where("destinationCity").is(req.getDestinationCity());
			criteriaList.add(criteria);
		}
		if (null != req.getLengthSpec()) {
			Criteria criteria = Criteria.where("lengthSpec").is(req.getLengthSpec());
			criteriaList.add(criteria);
		}
		if (null != req.getTemperatureSpec()) {
			Criteria criteria = Criteria.where("temperatureSpecList").all(req.getTemperatureSpec());
			criteriaList.add(criteria);
		}
		if (null != req.getDate()) {
			DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
			Date date;
			try {
				date = formatter.parse(req.getDate());
				Criteria startCriteria = Criteria.where("startDate").lte(date);
				Criteria untilCriteria = Criteria.where("untilDate").gte(date);
				Criteria criteria = startCriteria.andOperator(untilCriteria);
				criteriaList.add(criteria);
			} catch (ParseException e) {
			}
		}
		Criteria[] andCriterias = new Criteria[criteriaList.size()];
		for (int step=0; step<criteriaList.size(); step++) {
			andCriterias[step] = criteriaList.get(step);
		}
		andCriteria.andOperator(andCriterias);
		query.addCriteria(andCriteria);
		Sort.Order order = new Sort.Order(Sort.Direction.DESC, "creationDate");
		Sort sort = new Sort(order);
		query.with(sort);
		query.with(pageable);
		
		List<VehicleService> list = mongoTemplate.find(query, VehicleService.class);
		for (VehicleService service : list) {
			result.add(service.mapForSearchResult());
		}
		
		return result;
	}
	
	public Map<String, String> completeVehicleServiceCounting(CompleteVehicleServiceCountingRequest req) {
		Map<String, String> map = new HashMap<String, String>();
		
		Query query = new Query();
		Criteria andCriteria = new Criteria();
		List<Criteria> criteriaList = new ArrayList<Criteria>();
		Criteria stockCriteria = Criteria.where("stock").gt(Integer.valueOf(0));
		criteriaList.add(stockCriteria);
		if (null != req.getOriginProvince()) {
			Criteria criteria = Criteria.where("originProvince").is(req.getOriginProvince());
			criteriaList.add(criteria);
		}
		if (null != req.getOriginCity()) {
			Criteria criteria = Criteria.where("originCity").is(req.getOriginCity());
			criteriaList.add(criteria);
		}
		if (null != req.getDestinationProvince()) {
			Criteria criteria = Criteria.where("destinationProvince").is(req.getDestinationProvince());
			criteriaList.add(criteria);
		}
		if (null != req.getDestinationCity()) {
			Criteria criteria = Criteria.where("destinationCity").is(req.getDestinationCity());
			criteriaList.add(criteria);
		}
		if (null != req.getLengthSpec()) {
			Criteria criteria = Criteria.where("lengthSpec").is(req.getLengthSpec());
			criteriaList.add(criteria);
		}
		if (null != req.getTemperatureSpec()) {
			if (logger.isDebugEnabled()) {
				logger.debug("temperature spec - " + req.getTemperatureSpec());
			}
			Criteria criteria = Criteria.where("temperatureSpecList").all(req.getTemperatureSpec());
			criteriaList.add(criteria);
		}
		if (null != req.getDate()) {
			DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
			Date date;
			try {
				date = formatter.parse(req.getDate());
				Criteria startCriteria = Criteria.where("startDate").lte(date);
				Criteria untilCriteria = Criteria.where("untilDate").gte(date);
				Criteria criteria = startCriteria.andOperator(untilCriteria);
				criteriaList.add(criteria);
			} catch (ParseException e) {
			}
		}
		Criteria[] andCriterias = new Criteria[criteriaList.size()];
		for (int step=0; step<criteriaList.size(); step++) {
			andCriterias[step] = criteriaList.get(step);
		}
		andCriteria.andOperator(andCriterias);
		query.addCriteria(andCriteria);
		
		long count = mongoTemplate.count(query, VehicleService.class);
		map.put("count", Long.valueOf(count).toString());
		
		return map;
	}
	
	public List<Map<String, Object>> searchWarehouseService(SearchWarehouseServiceRequest req, Pageable pageable) {
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		
		Query query = new Query();
		Criteria andCriteria = new Criteria();
		List<Criteria> criteriaList = new ArrayList<Criteria>();
		Criteria stockCriteria = Criteria.where("stock").gt(Integer.valueOf(0));
		criteriaList.add(stockCriteria);
		if (null != req.getInDate()) {
			DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
			try {
				Date date = formatter.parse(req.getInDate());
				Criteria startCriteria = Criteria.where("startDate").lte(date);
				criteriaList.add(startCriteria);
			} catch (ParseException e) {
			}
		}
		if (null != req.getOutDate()) {
			DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
			try {
				Date date = formatter.parse(req.getOutDate());
				Criteria untilCriteria = Criteria.where("untilDate").gte(date);
				criteriaList.add(untilCriteria);
			} catch (ParseException e) {
			}
		}
		if (null != req.getProvince()) {
			Criteria provinceCriteria = Criteria.where("province").is(req.getProvince());
			criteriaList.add(provinceCriteria);
		}
		if (null != req.getCity()) {
			Criteria cityCriteria = Criteria.where("city").is(req.getCity());
			criteriaList.add(cityCriteria);
		}
		if (null != req.getTemperatureSpec()) {
			Criteria temperatureCriteria = Criteria.where("temperatureSpec").is(req.getTemperatureSpec());
			criteriaList.add(temperatureCriteria);
		}
		if (null != req.getVolume()) {
			Criteria volumeCriteria = Criteria.where("stock").gte(req.getVolume());
			criteriaList.add(volumeCriteria);
		}
		Criteria[] andCriterias = new Criteria[criteriaList.size()];
		for (int step=0; step<criteriaList.size(); step++) {
			andCriterias[step] = criteriaList.get(step);
		}
		andCriteria.andOperator(andCriterias);
		query.addCriteria(andCriteria);
		Sort.Order order = new Sort.Order(Sort.Direction.DESC, "creationDate");
		Sort sort = new Sort(order);
		query.with(sort);
		query.with(pageable);
		
		List<WarehouseService> list = mongoTemplate.find(query, WarehouseService.class);
		for (WarehouseService service : list) {
			result.add(service.mapForSearchResult());
		}
		
		return result;
	}
	
	public Map<String, String> warehouseServiceCounting(WarehouseServiceCountingRequest req) {
		Map<String, String> map = new HashMap<String, String>();
		
		Query query = new Query();
		Criteria andCriteria = new Criteria();
		List<Criteria> criteriaList = new ArrayList<Criteria>();
		Criteria stockCriteria = Criteria.where("stock").gt(Integer.valueOf(0));
		criteriaList.add(stockCriteria);
		if (null != req.getInDate()) {
			DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
			try {
				Date date = formatter.parse(req.getInDate());
				Criteria startCriteria = Criteria.where("startDate").lte(date);
				criteriaList.add(startCriteria);
			} catch (ParseException e) {
			}
		}
		if (null != req.getOutDate()) {
			DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
			try {
				Date date = formatter.parse(req.getOutDate());
				Criteria untilCriteria = Criteria.where("untilDate").gte(date);
				criteriaList.add(untilCriteria);
			} catch (ParseException e) {
			}
		}
		if (null != req.getProvince()) {
			Criteria provinceCriteria = Criteria.where("province").is(req.getProvince());
			criteriaList.add(provinceCriteria);
		}
		if (null != req.getCity()) {
			Criteria cityCriteria = Criteria.where("city").is(req.getCity());
			criteriaList.add(cityCriteria);
		}
		if (null != req.getTemperatureSpec()) {
			Criteria temperatureCriteria = Criteria.where("temperatureSpec").is(req.getTemperatureSpec());
			criteriaList.add(temperatureCriteria);
		}
		if (null != req.getVolume()) {
			Criteria volumeCriteria = Criteria.where("stock").gte(req.getVolume());
			criteriaList.add(volumeCriteria);
		}
		Criteria[] andCriterias = new Criteria[criteriaList.size()];
		for (int step=0; step<criteriaList.size(); step++) {
			andCriterias[step] = criteriaList.get(step);
		}
		andCriteria.andOperator(andCriterias);
		query.addCriteria(andCriteria);
		
		long count = mongoTemplate.count(query, WarehouseService.class);
		map.put("count", Long.valueOf(count).toString());
		
		return map;
	}
	
	public List<Map<String, Object>> searchProductService(SearchProductServiceRequest req, Pageable pageable) {
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		
		Query query = new Query();
		Criteria andCriteria = new Criteria();
		List<Criteria> criteriaList = new ArrayList<Criteria>();
		Criteria stockCriteria = Criteria.where("stock").gt(Integer.valueOf(0));
		criteriaList.add(stockCriteria);
		Criteria dateCriteria = Criteria.where("availableUntil").gte(new Date());
		criteriaList.add(dateCriteria);
		if (null != req.getCategory()) {
			Criteria categoryCriteria = Criteria.where("category").is(req.getCategory());
			criteriaList.add(categoryCriteria);
		}
		if (null != req.getProvince()) {
			Criteria provinceCriteria = Criteria.where("province").is(req.getProvince());
			criteriaList.add(provinceCriteria);
		}
		if (null != req.getCity()) {
			Criteria cityCriteria = Criteria.where("city").is(req.getCity());
			criteriaList.add(cityCriteria);
		}
		
		Criteria[] andCriterias = new Criteria[criteriaList.size()];
		for (int step=0; step<criteriaList.size(); step++) {
			andCriterias[step] = criteriaList.get(step);
		}
		andCriteria.andOperator(andCriterias);
		query.addCriteria(andCriteria);
		Sort.Order order = new Sort.Order(Sort.Direction.DESC, "creationDate");
		Sort sort = new Sort(order);
		query.with(sort);
		query.with(pageable);
		
		List<ProductService> list = mongoTemplate.find(query, ProductService.class);
		for (ProductService service : list) {
			result.add(service.mapForSearchResult());
		}
		
		return result;
	}
	
	public Map<String, String> productServiceCounting(ProductServiceCountingRequest req) {
		Map<String, String> map = new HashMap<String, String>();
		
		Query query = new Query();
		Criteria andCriteria = new Criteria();
		List<Criteria> criteriaList = new ArrayList<Criteria>();
		Criteria stockCriteria = Criteria.where("stock").gt(Integer.valueOf(0));
		criteriaList.add(stockCriteria);
		Criteria dateCriteria = Criteria.where("availableUntil").gte(new Date());
		criteriaList.add(dateCriteria);
		if (null != req.getCategory()) {
			Criteria categoryCriteria = Criteria.where("category").is(req.getCategory());
			criteriaList.add(categoryCriteria);
		}
		if (null != req.getProvince()) {
			Criteria provinceCriteria = Criteria.where("province").is(req.getProvince());
			criteriaList.add(provinceCriteria);
		}
		if (null != req.getCity()) {
			Criteria cityCriteria = Criteria.where("city").is(req.getCity());
			criteriaList.add(cityCriteria);
		}
		
		Criteria[] andCriterias = new Criteria[criteriaList.size()];
		for (int step=0; step<criteriaList.size(); step++) {
			andCriterias[step] = criteriaList.get(step);
		}
		andCriteria.andOperator(andCriterias);
		query.addCriteria(andCriteria);
		
		long count = mongoTemplate.count(query, ProductService.class);
		map.put("count", Long.valueOf(count).toString());
		
		return map;
	}
	
	public List<Map<String, Object>> vehiclesForCompleteVehicleService(String serviceId) {
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
		
		// locate vehicle service
		Query vehicleServiceQuery = new Query();
		Criteria vehicleServiceCriteria = Criteria.where("id").is(serviceId);
		vehicleServiceQuery.addCriteria(vehicleServiceCriteria);
		VehicleService vehicleService = mongoTemplate.findOne(vehicleServiceQuery, VehicleService.class);
		if (null == vehicleService) {
			if (logger.isWarnEnabled()) {
				logger.warn("unable to locate vehicle service, id: " + serviceId);
			}
			return result;
		}
		
		// locate vehicle spec
		Query vehicleSpecQuery = new Query();
		Criteria vehicleSpecCriteria = Criteria.where("keySpec").is(vehicleService.getKeySpec());
		vehicleSpecQuery.addCriteria(vehicleSpecCriteria);
		VehicleSpec vehicleSpec = mongoTemplate.findOne(vehicleSpecQuery, VehicleSpec.class);
		if (null == vehicleSpec) {
			if (logger.isWarnEnabled()) {
				logger.warn("unable to locate vehicle spec, id: " + serviceId + " spec: " + vehicleService.getKeySpec());
			}
			return result;
		}
		
		// find vehicles
		Query vehicleQuery = new Query();
		Criteria vehicleCriteria = Criteria.where("licensePlate").in(vehicleSpec.getVehicles());
		vehicleQuery.addCriteria(vehicleCriteria);
		List<Vehicle> vehicles = mongoTemplate.find(vehicleQuery, Vehicle.class);
		for (Vehicle vehicle : vehicles) {
			result.add(vehicle.toSearchableMap());
		}
		
		return result;
	}
	
	public Map<String, Object> warehouseForWarehouseService(String serviceId) {
		Map<String, Object> map = new HashMap<String, Object>();
		
		// locate warehouse service
		Query warehouseServiceQuery = new Query();
		Criteria warehouseServiceCriteria = Criteria.where("id").is(serviceId);
		warehouseServiceQuery.addCriteria(warehouseServiceCriteria);
		WarehouseService warehouseService = mongoTemplate.findOne(warehouseServiceQuery, WarehouseService.class);
		if (null == warehouseService) {
			if (logger.isWarnEnabled()) {
				logger.warn("warehouseForWarehouseService - unable to locate warehouse service by id: " + serviceId);
			}
			return map;
		}
		
		// locate warehouse
		Query warehouseQuery = new Query();
		Criteria warehouseIdCriteria = Criteria.where("userId").is(warehouseService.getUserId());
		Criteria warehouseNameCriteria = Criteria.where("name").is(warehouseService.getName());
		Criteria warehouseCriteria = new Criteria();
		warehouseCriteria.andOperator(warehouseIdCriteria, warehouseNameCriteria);
		warehouseQuery.addCriteria(warehouseCriteria);
		Warehouse warehouse = mongoTemplate.findOne(warehouseQuery, Warehouse.class);
		if (null == warehouse) {
			return map;
		} else {
			return warehouse.toSearchableMap();
		}
	}
	
	public Map<String, Object> productForProductService(String serviceId) {
		Map<String, Object> map = new HashMap<String, Object>();

		// locate product service
		Query productServiceQuery = new Query();
		Criteria productServiceCriteria = Criteria.where("id").is(serviceId);
		productServiceQuery.addCriteria(productServiceCriteria);
		ProductService productService = mongoTemplate.findOne(productServiceQuery, ProductService.class);
		if (null == productService) {
			if (logger.isWarnEnabled()) {
				logger.warn("productForProductService - unable to locate product service by id: " + serviceId);
			}
			return map;
		}
		
		// locate product
		Query productQuery = new Query();
		Criteria productIdCriteria = Criteria.where("userId").is(productService.getUserId());
		Criteria productNameCriteria = Criteria.where("name").is(productService.getName());
		Criteria productCriteria = new Criteria();
		productCriteria.andOperator(productIdCriteria, productNameCriteria);
		productQuery.addCriteria(productCriteria);
		Product product = mongoTemplate.findOne(productQuery, Product.class);
		if (null == product) {
			return map;
		} else {
			return product.toSearchableMap();
		}
	}
	
	public WarehouseService findWarehouseServiceById(String serviceId) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("id").is(serviceId);
		Criteria stockCriteria = Criteria.where("stock").gt(Integer.valueOf(0));
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, stockCriteria);
		query.addCriteria(andCriteria);
		
		return mongoTemplate.findOne(query, WarehouseService.class);
	}
	
	public ProductService findProductServiceById(String serviceId) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("id").is(serviceId);
		Criteria stockCriteria = Criteria.where("stock").gt(Integer.valueOf(0));
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, stockCriteria);
		query.addCriteria(andCriteria);
		
		return mongoTemplate.findOne(query, ProductService.class);
	}
	
	public VehicleService findVehicleServiceById(String serviceId) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("id").is(serviceId);
		Criteria stockCriteria = Criteria.where("stock").gt(Integer.valueOf(0));
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, stockCriteria);
		query.addCriteria(andCriteria);
		
		return mongoTemplate.findOne(query, VehicleService.class);
	}
	
	@PostConstruct
	public void populatePredefinedData() {
		populateTransportationTemperatureLevels();
		populateWarehouseTemperatureLevels();
		populateVehicleLengthDef();
		populateProductCategories();
		List<AndroidVersion> androidVersions = mongoTemplate.findAll(AndroidVersion.class);
		if ((null==androidVersions) || (0==androidVersions.size())) {
			AndroidVersion androidVersion = new AndroidVersion();
			androidVersion.setAndroidVersion("1.0.0");
			mongoTemplate.save(androidVersion);
		}
		
		// create indexes
		mongoTemplate.indexOps(Warehouse.class).ensureIndex(new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_2DSPHERE));
	}
	
	protected void populateTransportationTemperatureLevels() {
		List<TransportationTemperature> temperatureLevels = mongoTemplate.findAll(TransportationTemperature.class);
		if ((null==temperatureLevels) || (0==temperatureLevels.size())) {
			// add fresh keeping level
			TransportationTemperature freshKeeping = new TransportationTemperature();
			freshKeeping.setIdentifier(TransportationTemperature.IDENTIFIER_FRESH_KEEPING);
			freshKeeping.setPresentingString("保鲜类 (2℃～8℃)");
			freshKeeping.setTemperatureMin(Integer.valueOf(2));
			freshKeeping.setTemperatureMax(Integer.valueOf(8));
			freshKeeping.setSorting(Integer.valueOf(0));
			mongoTemplate.save(freshKeeping);
			
			// add cold level
			TransportationTemperature cold = new TransportationTemperature();
			cold.setIdentifier(TransportationTemperature.IDENTIFIER_COLD);
			cold.setPresentingString("冷鲜类 (0℃～-5℃)");
			cold.setTemperatureMin(Integer.valueOf(-5));
			cold.setTemperatureMax(Integer.valueOf(0));
			cold.setSorting(Integer.valueOf(1));
			mongoTemplate.save(cold);
			
			// add freeze level
			TransportationTemperature freeze = new TransportationTemperature();
			freeze.setIdentifier(TransportationTemperature.IDENTIFIER_FREEZE);
			freeze.setPresentingString("冷冻类 (-10℃～-18℃)");
			freeze.setTemperatureMin(Integer.valueOf(-18));
			freeze.setTemperatureMax(Integer.valueOf(-10));
			freeze.setSorting(Integer.valueOf(2));
			mongoTemplate.save(freeze);
			
			// add deep freeze level
			TransportationTemperature deepFreeze = new TransportationTemperature();
			deepFreeze.setIdentifier(TransportationTemperature.IDENTIFIER_DEEP_FREEZE);
			deepFreeze.setPresentingString("深冷冻 (-20℃～-45℃)");
			deepFreeze.setTemperatureMin(Integer.valueOf(-45));
			deepFreeze.setTemperatureMax(Integer.valueOf(-20));
			deepFreeze.setSorting(Integer.valueOf(3));
			mongoTemplate.save(deepFreeze);
		}
	}
	
	protected void populateProductCategories() {
		List<ProductCategory> categories = mongoTemplate.findAll(ProductCategory.class);
		if ((null==categories) || (0==categories.size())) {
			// add fresh fruit
			ProductCategory freshFruit = new ProductCategory();
			freshFruit.setIdentifier(ProductCategory.CATEGORY_FRESH_FRUIT);
			freshFruit.setPresentingString("新鲜水果");
			freshFruit.setSorting(Integer.valueOf(0));
			mongoTemplate.save(freshFruit);
			
			// add fresh vegetable
			ProductCategory freshVegetable = new ProductCategory();
			freshVegetable.setIdentifier(ProductCategory.CATEGORY_FRESH_VEGETABLE);
			freshVegetable.setPresentingString("新鲜蔬菜");
			freshVegetable.setSorting(Integer.valueOf(1));
			mongoTemplate.save(freshVegetable);
			
			// add seafood
			ProductCategory seafood = new ProductCategory();
			seafood.setIdentifier(ProductCategory.CATEGORY_SEAFOOD);
			seafood.setPresentingString("海鲜水产");
			seafood.setSorting(Integer.valueOf(2));
			mongoTemplate.save(seafood);
			
			// add egg
			ProductCategory egg = new ProductCategory();
			egg.setIdentifier(ProductCategory.CATEGORY_EGG);
			egg.setPresentingString("精选蛋品");
			egg.setSorting(Integer.valueOf(3));
			mongoTemplate.save(egg);
			
			// add meat
			ProductCategory meat = new ProductCategory();
			meat.setIdentifier(ProductCategory.CATEGORY_MEAT);
			meat.setPresentingString("精选肉类");
			meat.setSorting(Integer.valueOf(4));
			mongoTemplate.save(meat);
			
			// add bird
			ProductCategory bird = new ProductCategory();
			bird.setIdentifier(ProductCategory.CATEGORY_BIRD);
			bird.setPresentingString("鸡鸭禽类");
			bird.setSorting(Integer.valueOf(5));
			mongoTemplate.save(bird);
			
			// add fast food
			ProductCategory fastFood = new ProductCategory();
			fastFood.setIdentifier(ProductCategory.CATEGORY_FAST_FOOD);
			fastFood.setPresentingString("速食面点");
			fastFood.setSorting(Integer.valueOf(6));
			mongoTemplate.save(fastFood);
			
			// add cooked food
			ProductCategory cookedFood = new ProductCategory();
			cookedFood.setIdentifier(ProductCategory.CATEGORY_COOKED_FOOD);
			cookedFood.setPresentingString("熟食");
			cookedFood.setSorting(Integer.valueOf(7));
			mongoTemplate.save(cookedFood);
		}
	}
	
	protected void populateWarehouseTemperatureLevels() {
		List<WarehouseTemperature> temperatureLevels = mongoTemplate.findAll(WarehouseTemperature.class);
		if ((null==temperatureLevels) || (0==temperatureLevels.size())) {
			// add high temperature level
			WarehouseTemperature highTemperature = new WarehouseTemperature();
			highTemperature.setIdentifier(WarehouseTemperature.IDENTIFIER_HIGH_TEMPERATURE);
			highTemperature.setPresentingString("高温库 (5℃～15℃)");
			highTemperature.setTemperatureMin(Integer.valueOf(5));
			highTemperature.setTemperatureMax(Integer.valueOf(15));
			highTemperature.setSorting(Integer.valueOf(0));
			mongoTemplate.save(highTemperature);
			
			// add medium temperature level
			WarehouseTemperature mediumTemperature = new WarehouseTemperature();
			mediumTemperature.setIdentifier(WarehouseTemperature.IDENTIFIER_MEDIUM_TEMPERATURE);
			mediumTemperature.setPresentingString("中温库 (5℃～-5℃)");
			mediumTemperature.setTemperatureMin(Integer.valueOf(-5));
			mediumTemperature.setTemperatureMax(Integer.valueOf(5));
			mediumTemperature.setSorting(Integer.valueOf(1));
			mongoTemplate.save(mediumTemperature);
			
			// add low temperature level
			WarehouseTemperature lowTemperature = new WarehouseTemperature();
			lowTemperature.setIdentifier(WarehouseTemperature.IDENTIFIER_LOW_TEMPERATURE);
			lowTemperature.setPresentingString("低温库 (-18℃～-25℃)");
			lowTemperature.setTemperatureMin(Integer.valueOf(-25));
			lowTemperature.setTemperatureMax(Integer.valueOf(-18));
			lowTemperature.setSorting(Integer.valueOf(2));
			mongoTemplate.save(lowTemperature);
			
			// add freeze level
			WarehouseTemperature freezeTemperature = new WarehouseTemperature();
			freezeTemperature.setIdentifier(WarehouseTemperature.IDENTIFIER_FREEZE);
			freezeTemperature.setPresentingString("速冻库 (-35℃～-40℃)");
			freezeTemperature.setTemperatureMin(Integer.valueOf(-40));
			freezeTemperature.setTemperatureMax(Integer.valueOf(-35));
			freezeTemperature.setSorting(Integer.valueOf(3));
			mongoTemplate.save(freezeTemperature);
			
			// add deep freeze level
			WarehouseTemperature deepFreezeTemperature = new WarehouseTemperature();
			deepFreezeTemperature.setIdentifier(WarehouseTemperature.IDENTIFIER_DEEP_FREEZE);
			deepFreezeTemperature.setPresentingString("深冷库 (-45℃～-60℃)");
			deepFreezeTemperature.setTemperatureMin(Integer.valueOf(-60));
			deepFreezeTemperature.setTemperatureMax(Integer.valueOf(-45));
			deepFreezeTemperature.setSorting(Integer.valueOf(4));
			mongoTemplate.save(deepFreezeTemperature);
		}
	}
	
	protected void populateVehicleLengthDef() {
		List<VehicleLengthDef> lengthDef = mongoTemplate.findAll(VehicleLengthDef.class);
		if ((null==lengthDef) || (0==lengthDef.size())) {
			// less than 3.8 meters
			VehicleLengthDef lt_3p8 = new VehicleLengthDef();
			lt_3p8.setIdentifier(VehicleLengthDef.DEF_LESS_THAN_3P8_METERS);
			lt_3p8.setPresentingString("3.8米以下");
			lt_3p8.setSorting(Integer.valueOf(0));
			mongoTemplate.save(lt_3p8);
			
			// 3.8 meters
			VehicleLengthDef e_3p8 = new VehicleLengthDef();
			e_3p8.setIdentifier(VehicleLengthDef.DEF_3P8_METERS);
			e_3p8.setPresentingString("3.8米");
			e_3p8.setSorting(Integer.valueOf(1));
			mongoTemplate.save(e_3p8);
			
			// 4.2 meters
			VehicleLengthDef e_4p2 = new VehicleLengthDef();
			e_4p2.setIdentifier(VehicleLengthDef.DEF_4P2_METERS);
			e_4p2.setPresentingString("4.2米");
			e_4p2.setSorting(Integer.valueOf(2));
			mongoTemplate.save(e_4p2);
			
			// 4.8 meters
			VehicleLengthDef e_4p8 = new VehicleLengthDef();
			e_4p8.setIdentifier(VehicleLengthDef.DEF_4P8_METERS);
			e_4p8.setPresentingString("4.8米");
			e_4p8.setSorting(Integer.valueOf(3));
			mongoTemplate.save(e_4p8);
			
			// 5.8 meters
			VehicleLengthDef e_5p8 = new VehicleLengthDef();
			e_5p8.setIdentifier(VehicleLengthDef.DEF_5P8_METERS);
			e_5p8.setPresentingString("5.8米");
			e_5p8.setSorting(Integer.valueOf(4));
			mongoTemplate.save(e_5p8);
			
			// 6.2 meters
			VehicleLengthDef e_6p2 = new VehicleLengthDef();
			e_6p2.setIdentifier(VehicleLengthDef.DEF_6P2_METERS);
			e_6p2.setPresentingString("6.2米");
			e_6p2.setSorting(Integer.valueOf(5));
			mongoTemplate.save(e_6p2);
			
			// 6.8 meters
			VehicleLengthDef e_6p8 = new VehicleLengthDef();
			e_6p8.setIdentifier(VehicleLengthDef.DEF_6P8_METERS);
			e_6p8.setPresentingString("6.8米");
			e_6p8.setSorting(Integer.valueOf(6));
			mongoTemplate.save(e_6p8);
			
			// 7.4 meters
			VehicleLengthDef e_7p4 = new VehicleLengthDef();
			e_7p4.setIdentifier(VehicleLengthDef.DEF_7P4_METERS);
			e_7p4.setPresentingString("7.4米");
			e_7p4.setSorting(Integer.valueOf(7));
			mongoTemplate.save(e_7p4);
			
			// 7.8 meters
			VehicleLengthDef e_7p8 = new VehicleLengthDef();
			e_7p8.setIdentifier(VehicleLengthDef.DEF_7P8_METERS);
			e_7p8.setPresentingString("7.8米");
			e_7p8.setSorting(Integer.valueOf(8));
			mongoTemplate.save(e_7p8);
			
			// 8.6 meters
			VehicleLengthDef e_8p6 = new VehicleLengthDef();
			e_8p6.setIdentifier(VehicleLengthDef.DEF_8P6_METERS);
			e_8p6.setPresentingString("8.6米");
			e_8p6.setSorting(Integer.valueOf(9));
			mongoTemplate.save(e_8p6);
			
			// 9.6 meters
			VehicleLengthDef e_9p6 = new VehicleLengthDef();
			e_9p6.setIdentifier(VehicleLengthDef.DEF_9P6_METERS);
			e_9p6.setPresentingString("9.6米");
			e_9p6.setSorting(Integer.valueOf(10));
			mongoTemplate.save(e_9p6);
			
			// between 13 and 15 meters
			VehicleLengthDef b13and15 = new VehicleLengthDef();
			b13and15.setIdentifier(VehicleLengthDef.DEF_BETWEEN_13_AND_15_METERS);
			b13and15.setPresentingString("13米~15米");
			b13and15.setSorting(Integer.valueOf(11));
			mongoTemplate.save(b13and15);
			
			// longer than 15 meters
			VehicleLengthDef lt15 = new VehicleLengthDef();
			lt15.setIdentifier(VehicleLengthDef.DEF_LONGER_THAN_15_METERS);
			lt15.setPresentingString("15米以上");
			lt15.setSorting(Integer.valueOf(12));
			mongoTemplate.save(lt15);
		}
	}

	@Autowired
	private MongoTemplate mongoTemplate;
	
	@Autowired
	private GridFsTemplate gridFsTemplate;

	private static final Logger logger = LoggerFactory.getLogger(ProductDataManager.class);
}
